%%% Copyright (C) 2017  Tomas Abrahamsson
%%%
%%% Author: Tomas Abrahamsson <tab@lysator.liu.se>
%%%
%%% This library is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU Lesser General Public
%%% License as published by the Free Software Foundation; either
%%% version 2.1 of the License, or (at your option) any later version.
%%%
%%% This library is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
%%% Lesser General Public License for more details.
%%%
%%% You should have received a copy of the GNU Lesser General Public
%%% License along with this library; if not, write to the Free Software
%%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
%%% MA  02110-1301  USA

%%% @doc Generation of nif C++ code that calls C++ code generated by Google's
%%% protobuf compiler (protoc).
%%%
%%% Here is also code for generating the load_nif/0 erlang function
%%% that actually loads the nif C++ code.
%%%
%%% @private

-module(gpb_gen_nif).

-export([format_load_nif/2]).
-export([format_nif_encoder_error_wrappers/3]).
-export([format_nif_decoder_error_wrappers/3]).
-export([format_nif_to_json_error_wrappers/3]).
-export([format_nif_from_json_error_wrappers/3]).
-export([format_nif_cc/4]).

-include("../include/gpb.hrl").
-include("gpb_codegen.hrl").
-include("gpb_compile.hrl").

-import(gpb_lib, [replace_term/2]).

-record(cc_enum, {type  :: string(),
                  enums :: [{atom(), string()}],
                  unaliased_enums :: [{atom(), string()}]}).
-record(cc_msg, {type :: string()}).

format_load_nif(Mod, Opts) ->
    VsnAsList = gpb:version_as_list(),
    case proplists:get_value(load_nif, Opts, '$undefined') of
        '$undefined' ->
            ["load_nif() ->\n",
             %% Note: using ?MODULE here has impacts on compiling to
             %% binary, because we don't pass it through the preprocessor
             %% maybe we should?
             "    SelfDir = filename:dirname(code:which(?MODULE)),\n",
             "    NifDir = case lists:reverse(filename:split(SelfDir)) of\n"
             "                 [\"ebin\" | PDR] ->\n"
             "                      PD = filename:join(lists:reverse(PDR)),\n",
             "                      filename:join(PD, \"priv\");\n",
             "                 _ ->\n",
             "                      SelfDir\n",
             "             end,\n",
             "    NifBase = \"", atom_to_list(Mod) ++ ".nif", "\",\n",
             "    Nif = filename:join(NifDir, NifBase),\n",
             ?f("    erlang:load_nif(Nif, ~w).\n", [VsnAsList])];
        LoadNifFnText when is_list(LoadNifFnText); is_binary(LoadNifFnText) ->
            [replace_tilde_s(iolist_to_binary(LoadNifFnText),
                             iolist_to_binary(?f("\"~s.nif\"", [Mod])),
                             iolist_to_binary(?f("~w", [VsnAsList])))]
    end.

replace_tilde_s(<<"{{nifbase}}", Rest/binary>>, ModBin, VsnBin) ->
    <<ModBin/binary, (replace_tilde_s(Rest, ModBin, VsnBin))/binary>>;
replace_tilde_s(<<"{{loadinfo}}", Rest/binary>>, ModBin, VsnBin) ->
    <<VsnBin/binary, (replace_tilde_s(Rest, ModBin, VsnBin))/binary>>;
replace_tilde_s(<<C, Rest/binary>>, ModBin, VsnBin) ->
    <<C, (replace_tilde_s(Rest, ModBin, VsnBin))/binary>>;
replace_tilde_s(<<>>, _ModBin, _VsnBin) ->
    <<>>.

%% error wrappers for encoders
format_nif_encoder_error_wrappers(Defs, _AnRes, _Opts) ->
    [format_msg_nif_encode_error_wrapper(MsgName)
     || {{msg, MsgName}, _MsgDef} <- Defs].

format_msg_nif_encode_error_wrapper(MsgName) ->
    gpb_codegen:format_fn(
      gpb_lib:mk_fn(encode_msg_, MsgName),
      fun(Msg) ->
              erlang:nif_error({error,{nif_not_loaded,'<msg-name>'}}, [Msg])
      end,
      [replace_term('<msg-name>', MsgName)]).

%% error wrappers for decoders
format_nif_decoder_error_wrappers(Defs, _AnRes, _Opts) ->
    [format_msg_nif_decode_error_wrapper(MsgName)
     || {{msg, MsgName}, _MsgDef} <- Defs].

format_msg_nif_decode_error_wrapper(MsgName) ->
    gpb_codegen:format_fn(
      gpb_lib:mk_fn(decode_msg_, MsgName),
      fun(Bin) ->
              erlang:nif_error({error,{nif_not_loaded,'<msg-name>'}}, [Bin])
      end,
      [replace_term('<msg-name>', MsgName)]).

%% error wrappers for to_json
format_nif_to_json_error_wrappers(Defs, _AnRes, _Opts) ->
    [format_msg_nif_to_json_error_wrapper(MsgName)
     || {{msg, MsgName}, _MsgDef} <- Defs].

format_msg_nif_to_json_error_wrapper(MsgName) ->
    gpb_codegen:format_fn(
      gpb_lib:mk_fn(to_json_msg_, MsgName),
      fun(Msg) ->
              erlang:nif_error({error,{nif_not_loaded,'<msg-name>'}}, [Msg])
      end,
      [replace_term('<msg-name>', MsgName)]).

%% error wrappers for from_json
format_nif_from_json_error_wrappers(Defs, _AnRes, _Opts) ->
    [format_msg_nif_from_json_error_wrapper(MsgName)
     || {{msg, MsgName}, _MsgDef} <- Defs].

format_msg_nif_from_json_error_wrapper(MsgName) ->
    gpb_codegen:format_fn(
      gpb_lib:mk_fn(from_json_msg_, MsgName),
      fun(JBin) ->
              erlang:nif_error({error,{nif_not_loaded,'<msg-name>'}}, [JBin])
      end,
      [replace_term('<msg-name>', MsgName)]).

%% -- NIF C++ code ---

format_nif_cc(Mod, Defs, AnRes, Opts) ->
    DoJson = gpb_lib:json_by_opts(Opts),
    CCMapping = calc_cc_mapping(Defs, AnRes, Opts),
    iolist_to_binary(
      [format_nif_cc_includes(Mod, Defs, AnRes, Opts),
       format_nif_cc_oneof_version_check_if_present(Defs),
       format_nif_cc_maptype_version_check_if_present(Defs),
       format_nif_cc_proto3_version_check_if_present(Defs),
       format_nif_cc_map_api_check_if_needed(Opts),
       format_nif_cc_json_api_check_if_needed(Opts),
       format_nif_cc_json_includes_if_needed(Opts),
       format_nif_cc_byte_size_macros(Defs),
       format_nif_cc_local_function_decls(Mod, Defs, CCMapping, AnRes, Opts),
       format_nif_cc_mk_consts(Mod, Defs, AnRes, Opts),
       format_nif_cc_mk_atoms(Mod, Defs, AnRes, Opts),
       format_nif_cc_mk_is_key(Mod, Defs, AnRes, Opts),
       format_nif_cc_mk_mk_key(Mod, Defs, AnRes, Opts),
       format_nif_cc_utf8_conversion(Mod, Defs, AnRes, Opts),
       format_nif_cc_unknown_pack_unpack(AnRes),
       format_nif_cc_encoders(Mod, Defs, CCMapping, Opts),
       format_nif_cc_packers(Mod, Defs, CCMapping, Opts),
       format_nif_cc_decoders(Mod, Defs, CCMapping, Opts),
       format_nif_cc_unpackers(Mod, Defs, CCMapping, Opts),
       [[format_nif_cc_to_jsoners(Mod, Defs, CCMapping, Opts),
         format_nif_cc_from_jsoners(Mod, Defs, CCMapping, Opts)] || DoJson],
       format_nif_cc_foot(Mod, Defs, Opts)]).


%% Create a mapping {msg,MsgName}   -> Info = #cc_msg{}
%%                  {enum,EnumName} -> Info = #cc_enum{}
calc_cc_mapping(Defs, #anres{renamings=Renamings}, Opts) ->
    UsesPackage = proplists:get_bool(use_packages, Opts),
    %% FIXME: take any renaming options into consideration as well
    {CCMapping, _Pkg, _Prefix} =
        lists:foldl(
          fun({package, Package}, {Acc, _Pkg, _PrevPkgPrefix}) ->
                  Package1 = gpb_names:original_pkg_name(Package, Renamings),
                  if UsesPackage ->
                          %% Names are already prepended with package name,
                          %% on dotted form
                          {Acc, atom_to_list(Package1), ""};
                     true ->
                          CPkgPrefix = "::" ++ dot_replace_s(Package1, "::"),
                          {Acc, atom_to_list(Package1), CPkgPrefix}
                  end;
             ({{msg, MsgName}, _Fields}, {Acc, Pkg, CPrefix}) ->
                  MsgName1 = gpb_names:original_msg_name(MsgName, Renamings),
                  CCType = CPrefix ++ "::" ++ dot_replace_s(MsgName1, "::"),
                  Info = #cc_msg{type=CCType},
                  {[{MsgName, Info} | Acc], Pkg, CPrefix};
             ({{group, GName}, _Fields}, {Acc, Pkg, CPrefix}) ->
                  GName1 = gpb_names:original_group_name(GName, Renamings),
                  CCType = CPrefix ++ "::" ++ dot_replace_s(GName1, "::"),
                  Info = #cc_msg{type=CCType},
                  {[{GName, Info} | Acc], Pkg, CPrefix};
             ({{enum, EnumName}, Enums}, {Acc, Pkg, CPrefix}) ->
                  %% If we have this proto
                  %%
                  %%    syntax="proto3";
                  %%    package x.y;
                  %%    message msg1 {
                  %%       top_level_enum f1 = 1;
                  %%       msg_level_enum f2 = 2;
                  %%       enum msg_level_enum { ba = 0; bb = 1; }
                  %%    }
                  %%    enum top_level_enum { aa = 0; ab = 1; }
                  %%
                  %% then we refer to them as follows in C++
                  %%
                  %%    ::x:y::top_level_enum         // the type name
                  %%    ::x:y::aa                     // an enum symbol
                  %%    ::x:y::msg1_msg_level_enum    // the type name
                  %%    ::x:y::msg1_msg_level_enum_ba // an enum symbol
                  %%
                  %% The C++ enums in .pb.h are declared in the namespace
                  %% but not inside any class.
                  %%
                  EnumName1 = gpb_names:original_enum_name(EnumName,
                                                           Renamings),
                  EnumPart = % eg: "top_level_enum" or "msg1.msg_level_enum"
                      if UsesPackage,
                         Pkg /= '$undefined' ->
                              split_enum_with_pkg(EnumName1, Pkg);
                         true ->
                              atom_to_list(EnumName1)
                      end,
                  CPkg = if Pkg /= '$undefined' ->
                                 "::" ++ dot_replace_s(Pkg, "::");
                            true -> ""
                         end,
                  CCType = (CPkg ++ "::" ++ dot_replace_s(EnumPart, "_")),
                  EPrefix = case is_dotted(EnumPart) of
                                false -> "";
                                true  -> dot_replace_s(EnumPart, "_") ++ "_"
                            end,
                  MkCCSym = fun(Sym) ->
                                    ?ff("~s::~s~s",
                                        [CPkg, EPrefix, 'sym_to_c++'(Sym)])
                            end,
                  CCEnums =
                      [{Sym, MkCCSym(Sym)} || {Sym, _Val} <- Enums],
                  UnaliasedEnums = gpb_lib:unalias_enum(Enums),
                  UnaliasedCCEnums =
                      [{Sym, MkCCSym(Sym)} || {Sym, _Val} <- UnaliasedEnums],
                  Info = #cc_enum{type=CCType,
                                  enums=CCEnums,
                                  unaliased_enums=UnaliasedCCEnums},
                  {[{EnumName, Info} | Acc], Pkg, CPrefix};
             (_Other, {Acc, Pkg, CPrefix}) ->
                  {Acc, Pkg, CPrefix}
          end,
          {[], '$undefined', ""},
          Defs),
    dict:from_list(CCMapping).

split_enum_with_pkg(EnumName, Pkg) ->
    EnumStr = atom_to_list(EnumName),
    NameParts = gpb_lib:string_lexemes(EnumStr, "."),
    PkgParts  = gpb_lib:string_lexemes(Pkg, "."),
    gpb_lib:dot_join(drop_prefix(PkgParts, NameParts)).

drop_prefix([], Rest) -> Rest;
drop_prefix([X | PrefixRest], [X | Rest]) -> drop_prefix(PrefixRest, Rest).

is_lite_rt(Defs) ->
    OptimizeOpts = [Opt || {option,{optimize_for,Opt}} <- Defs],
    lists:any(fun(OptOpt) -> OptOpt == 'LITE_RUNTIME' end,
              OptimizeOpts).

format_nif_cc_includes(Mod, Defs, AnRes, _Opts) ->
    IsLiteRT = is_lite_rt(Defs),
    ["#include <string.h>\n",
     "#include <string>\n",
     "\n",
     "#include <erl_nif.h>\n",
     "\n",
     ?f("#include \"~s.pb.h\"\n", [Mod]),
     ["#include <google/protobuf/message_lite.h>\n" || IsLiteRT],
     format_math_include(AnRes),
     "\n"].

format_math_include(AnRes) ->
    case is_any_field_of_type_float_or_double(AnRes) of
        true ->
            ["#include <math.h>\n",
             "#include <cmath>\n",
             "#ifndef isnan\n",
             "# define isnan std::isnan\n",
             "#endif\n",
             "\n",
             "#ifndef isinf\n",
             "# define isinf std::isinf\n",
             "#endif\n",
            "\n"];
        false ->
            ""
    end.

format_nif_cc_oneof_version_check_if_present(Defs) ->
    case contains_oneof(Defs) of
        true ->
            ["#if GOOGLE_PROTOBUF_VERSION < 2006000\n"
             "#error \"The proto definitions contain 'oneof' fields.\"\n"
             "#error \"This feature appeared in protobuf 2.6.0, but\"\n"
             "#error \"it appears your protobuf is older.  Please\"\n"
             "#error \"update protobuf.\"\n"
             "#endif\n"
             "\n"];
        false ->
            ""
    end.

contains_oneof([{{msg,_}, Fields} | Rest]) ->
    case lists:any(fun(F) -> is_record(F, gpb_oneof) end, Fields) of
        false -> contains_oneof(Rest);
        true  -> true
    end;
contains_oneof([_ | Rest]) ->
    contains_oneof(Rest);
contains_oneof([]) ->
    false.

format_nif_cc_maptype_version_check_if_present(Defs) ->
    case contains_maptype_field(Defs) of
        true ->
            ["#if GOOGLE_PROTOBUF_VERSION < 3000000\n"
             "#error \"The proto definitions contain 'map' fields.\"\n"
             "#error \"This feature appeared in protobuf 3, but\"\n"
             "#error \"it appears your protobuf is older.  Please\"\n"
             "#error \"update protobuf.\"\n"
             "#endif\n"
             "\n"];
        false ->
            ""
    end.

contains_maptype_field([{{msg,_}, Fields} | Rest]) ->
    case lists:any(fun  is_maptype_field/1, Fields) of
        false -> contains_maptype_field(Rest);
        true  -> true
    end;
contains_maptype_field([_ | Rest]) ->
    contains_maptype_field(Rest);
contains_maptype_field([]) ->
    false.

is_maptype_field(#?gpb_field{type={map,_,_}}) -> true;
is_maptype_field(_) -> false.

format_nif_cc_proto3_version_check_if_present(Defs) ->
    case proplists:get_value(syntax, Defs) of
        "proto3" ->
            ["#if GOOGLE_PROTOBUF_VERSION < 3000000\n"
             "#error \"The proto definitions use 'proto3' syntax.\"\n"
             "#error \"This feature appeared in protobuf 3, but\"\n"
             "#error \"it appears your protobuf is older.  Please\"\n"
             "#error \"update protobuf.\"\n"
             "#endif\n"
             "\n"];
        _ ->
            ""
    end.

format_nif_cc_map_api_check_if_needed(Opts) ->
    case gpb_lib:get_2tuples_or_maps_for_maptype_fields_by_opts(Opts) of
        '2tuples' ->
            "";
        maps ->
            %% The maps api functions appeared in erl_nif.h version 2.6,
            %% which is Erlang 17, but they were not documented until 18.0.
            %% There were some changes to the iterators in 2.8 (= Erlang 18.0)
            %% but those are not needed.
            ["#if (!(", format_nif_check_version_or_later(2, 6), "))\n"
             "#error \"Maps was specified. The needed nif interface for\"\n"
             "#error \"maps appeared in version 2.6 (Erlang 17), but\"\n"
             "#error \"it appears your erl_nif version is older.  Please\"\n"
             "#error \"update Erlang.\"\n"
             "#endif\n"
             "\n"]
    end.

format_nif_cc_json_api_check_if_needed(Opts) ->
    case gpb_lib:json_by_opts(Opts) of
        true ->
            PreserveFNames =
                proplists:get_bool(json_preserve_proto_field_names, Opts),
            CaseInsensitiveEnums =
                proplists:get_value(json_case_insensitive_enum_parsing, Opts),
            ["#if GOOGLE_PROTOBUF_VERSION < 3000000\n"
             "#error \"The json option was specified,\"\n"
             "#error \"this feature appeared in protobuf 3, but\"\n"
             "#error \"it appears your protobuf is older.  Please\"\n"
             "#error \"update protobuf.\"\n"
             "#endif\n",
             if PreserveFNames ->
                     ["\n"
                      "#if GOOGLE_PROTOBUF_VERSION < 3003000\n"
                      "#error \"The json_preserve_proto_field_names option\"\n"
                      "#error \"was set, but protobuf support for this\"\n"
                      "#error \"first appeared in protobuf 3.3.0, and\"\n"
                      "#error \"it appears your protobuf is older.  Please\"\n"
                      "#error \"update protobuf.\"\n"
                      "#endif\n"];
                true ->
                     ""
             end,
             if CaseInsensitiveEnums == true;
                CaseInsensitiveEnums == undefined -> % default is true
                     "";
                CaseInsensitiveEnums == false ->
                     ["\n"
                      "#if GOOGLE_PROTOBUF_VERSION < 3007000\n"
                      "#error \"The json_case_insensitive_enum_parsing\"\n"
                      "#error \"option was set to false, but support \"\n"
                      "#error \"for configuring this first appeared\"\n"
                      "#error \"in version 3.7.0, and it appears \"\n"
                      "#error \"your protobuf is older.  Please\"\n"
                      "#error \"update protobuf.\"\n"
                      "#endif\n"]
             end];
        false ->
            ""
    end.

format_nif_cc_json_includes_if_needed(Opts) ->
    case gpb_lib:json_by_opts(Opts) of
        true ->
            ["#include <google/protobuf/util/json_util.h>\n"];
        false ->
            ""
    end.

format_nif_cc_byte_size_macros(Defs) ->
    [["#if GOOGLE_PROTOBUF_VERSION >= 3007000\n"
      "#define BYTE_SIZE_TYPE size_t\n"
      "#define BYTE_SIZE_METHOD ByteSizeLong\n"
      "#else\n"
      "#define BYTE_SIZE_TYPE int\n"
      "#define BYTE_SIZE_METHOD ByteSize\n"
      "#endif\n"]
     || gpb_lib:contains_messages(Defs)].

format_nif_cc_local_function_decls(_Mod, Defs, CCMapping, AnRes, _Opts) ->
    #anres{unknowns_info=UnknownsInfo} = AnRes,
    [[begin
          PackFnName = mk_c_fn(p_msg_, MsgName),
          UnpackFnName = mk_c_fn(u_msg_, MsgName),
          #cc_msg{type=CMsgType} = dict:fetch(MsgName, CCMapping),
          [["static int ",PackFnName,["(ErlNifEnv *env, ",
                                      "const ERL_NIF_TERM r,",
                                      CMsgType," *m);\n"]],
           ["static ERL_NIF_TERM ",UnpackFnName,["(ErlNifEnv *env, ",
                                                 "const ",CMsgType," *m);\n"]]]
      end
      || MsgName <- gpb_lib:msg_or_group_names(Defs)],
     case UnknownsInfo of
         both_with_and_without_field_for_unknowns ->
             format_nif_cc_local_functions_for_unknowns();
         all_are_without_field_for_unknowns ->
             [];
         all_are_with_field_for_unknowns ->
             format_nif_cc_local_functions_for_unknowns();
         no_msgs ->
             []
     end,
     "\n"].

format_nif_cc_local_functions_for_unknowns() ->
    ["static int\n"
     "pack_unknown_fields(\n"
     "    ErlNifEnv *env,\n"
     "    ::google::protobuf::UnknownFieldSet *ufset,\n"
     "    ERL_NIF_TERM unknowns);\n"
     "static int\n"
     "pack_unknown_msg_fields(\n"
     "    ErlNifEnv *env,\n"
     "    ::google::protobuf::Message *m,\n"
     "    ERL_NIF_TERM unknowns);\n"
     "static ERL_NIF_TERM\n"
     "unpack_unknown_fields(\n"
     "    ErlNifEnv *env,\n"
     "    const ::google::protobuf::UnknownFieldSet &fields);\n"
     "static ERL_NIF_TERM\n"
     "unpack_unknown_msg_fields(\n"
     "    ErlNifEnv *env,\n"
     "    const ::google::protobuf::Message *m);\n"].

format_nif_cc_mk_atoms(_Mod, Defs, AnRes, Opts) ->
    Maps = gpb_lib:get_records_or_maps_by_opts(Opts) == maps,
    MapsKeyType = gpb_lib:get_maps_key_type_by_opts(Opts),
    #anres{unknowns_info=UnknownsInfo} = AnRes,
    %% About gpb_aa_<...> vs gpb_fa_<...>/gpb_xa_<...> C variables
    %% ----------------------------------------------
    %% gpb_aa_<...>:
    %% Things that are used as atoms will be in C variables gpb_aa_<...>
    %% These are:
    %% * constants: true, false, undefined, infinity, '-infinity', nan
    %% * message names (record names)a
    %% * enum symbols
    %% * oneof tags when _not_ flat oneofs
    %%
    %% gpb_fa_<...>
    %% gpb_xa_<...>
    %% Map fields names can be either atoms or binaries, depending on
    %% the maps_key_type option. Additionally, with maps_oneof = flat, the
    %% oneof tags will be map keys. Things that will be map keys,
    %% are stored in C variables gpb_fa_<...>. These are either
    %% atoms or C strings (char arrays) used for constructing and comparing
    %% binaries (the is_key and mk_key functions).
    %% Here are:
    %% * map key field names, ie names of #?gpb_field{}s
    %% * #gpb_oneof{} field names when _not_ flat oneofs (avoid unused vars)
    %% * oneof tags when flat oneofs
    %%
    %% gpb_xa_<...> are primarily for fields for unknowns with
    %% fields names out-of-range of ordinary fields: '$unknown'
    %% in order to guarantee non-collide with ordinary field names.
    {AtomVars, FieldNameVars} = calc_atoms_fields(Defs, AnRes, Opts),
    AtomsForUnknowns = case UnknownsInfo of
                           both_with_and_without_field_for_unknowns ->
                               vars_atoms_for_unknowns();
                           all_are_without_field_for_unknowns ->
                               [];
                           all_are_with_field_for_unknowns ->
                               vars_atoms_for_unknowns();
                           no_msgs ->
                               []
                       end,
    [[?f("static ERL_NIF_TERM ~s;\n", [Var]) || {Var,_Atom} <- AtomVars],
     if Maps, MapsKeyType == binary ->
             [?f("static const char *~s = \"~s\";\n", [Var, Atom])
              || {Var,Atom} <- FieldNameVars];
        true ->
             [?f("static ERL_NIF_TERM ~s;\n", [Var])
              || {Var,_Atom} <- FieldNameVars]
     end,
     [?f("static ERL_NIF_TERM ~s;\n", [Var]) || {Var,_} <- AtomsForUnknowns],
     "\n",
     ["static void install_atoms(ErlNifEnv *env)\n"
      "{\n",
      [?f("    ~s = enif_make_atom(env, \"~s\");\n", [Var, Atom])
       || {Var, Atom} <- AtomVars],
      if Maps, MapsKeyType == binary ->
              "";
         true ->
              [?f("    ~s = enif_make_atom(env, \"~s\");\n", [Var, Atom])
               || {Var, Atom} <- FieldNameVars]
      end,
      [?f("    ~s = enif_make_atom(env, \"~s\");\n", [Var, Atom])
       || {Var, Atom} <- AtomsForUnknowns],
      "}\n",
      "\n"]].

calc_atoms_fields(Defs, AnRes, Opts) ->
    Maps = gpb_lib:get_records_or_maps_by_opts(Opts) == maps,
    MappingUnset = gpb_lib:get_mapping_and_unset_by_opts(Opts),
    FlatMaps = case MappingUnset of
                   #maps{unset_optional=omitted, oneof=flat} -> true;
                   _ -> false
               end,
    %% Calculate things that are to be always atoms
    Atoms0 = % deep list of atoms
        [[collect_record_names(Defs) || MappingUnset == records],
         collect_constants(AnRes),
         collect_enum_syms(Defs),
         [collect_oneof_tags(Defs) || not FlatMaps]],
    Atoms1 = deep_list_to_unqiue_list(Atoms0),
    AtomVars0 = [{mk_c_var(gpb_aa_, minus_to_m(A)), A} || A <- Atoms1],
    NoValue = case MappingUnset of
                  records -> undefined;
                  #maps{unset_optional=present_undefined} -> undefined;
                  #maps{unset_optional=omitted} -> '$undef'
              end,
    AtomVars1 = [{"gpb_x_no_value", NoValue} | AtomVars0],

    %% Calculate map fields names (atoms or eventually binaries depending on
    %% option)
    FieldNames0 = % deep list of atoms
        [[collect_toplevel_non_oneof_field_names(Defs) || Maps],
         [collect_oneof_names(Defs) || Maps, not FlatMaps],
         [collect_oneof_tags(Defs) || FlatMaps]],
    FieldNames = deep_list_to_unqiue_list(FieldNames0),
    FieldNameVars = [{mk_c_field_var(A), A} || A <- FieldNames],
    {AtomVars1, FieldNameVars}.

collect_record_names(Defs) ->
    [MsgName || {_, MsgName, _Fields} <- gpb_lib:msgs_or_groups(Defs)].

collect_enum_syms(Defs) ->
    [[Sym || {Sym, _V} <- EnumDef] || {{enum, _}, EnumDef} <- Defs].

collect_constants(AnRes) ->
    [case is_any_field_of_type_enum(AnRes) of
         true  -> [undefined];
         false -> []
     end,
     case is_any_field_of_type_bool(AnRes) of
         true  -> [true, false];
         false -> []
     end,
     case is_any_field_of_type_float_or_double(AnRes) of
         true  -> [infinity, '-infinity', nan];
         false -> []
     end].

collect_oneof_tags(Defs) ->
    [[[Tag
       || #?gpb_field{name=Tag} <- OFields]
      || #gpb_oneof{fields=OFields} <- Fields]
     || {_msg_or_group, _, Fields} <- gpb_lib:msgs_or_groups(Defs)].

collect_toplevel_non_oneof_field_names(Defs) ->
    [[FName || #?gpb_field{name=FName} <- Fields]
     || {_, _Name, Fields} <- gpb_lib:msgs_or_groups(Defs)].

collect_oneof_names(Defs) ->
    [[FName || #gpb_oneof{name=FName} <- Fields]
     || {_, _Name, Fields} <- gpb_lib:msgs_or_groups(Defs)].

deep_list_to_unqiue_list(DL) ->
    sets:to_list(deep_get_unique_aux(DL, sets:new())).

deep_get_unique_aux(L, InitAcc) when is_list(L) ->
    lists:foldl(fun deep_get_unique_aux/2, InitAcc, L);
deep_get_unique_aux(Elem, Acc) ->
    sets:add_element(Elem, Acc).

minus_to_m(A) ->
    case atom_to_list(A) of
        "-"++Rest -> "m"++Rest;
        _         -> A
    end.

vars_atoms_for_unknowns() ->
    Atoms = [varint, fixed64, length_delimited, group, fixed32],
    [{mk_c_var(gpb_ua_, A), A} || A <- Atoms].

format_nif_cc_mk_consts(_Mod, _Defs, AnRes, _Opts) ->
    case is_any_field_of_type_bool(AnRes) of
        true -> ["static ERL_NIF_TERM gpb_true_int;\n"
                 "static void install_consts(ErlNifEnv *env)\n"
                 "{\n",
                 "   gpb_true_int = enif_make_uint(env, 1);\n"
                 "}\n"];
        _ -> ["static void install_consts(ErlNifEnv *env)\n"
             "{\n",
             "}\n"]
    end.

format_nif_cc_mk_is_key(_Mod, _Defs, _AnRes, Opts) ->
    Maps = gpb_lib:get_records_or_maps_by_opts(Opts) == maps,
    MapsKeyType = gpb_lib:get_maps_key_type_by_opts(Opts),
    if Maps, MapsKeyType == binary ->
            ["static int is_key(ErlNifEnv *env,\n"
             "                  const ERL_NIF_TERM x,\n"
             "                  const char *key_to_check_for)\n",
             "{\n",
             "    ErlNifBinary xb;\n"
             "    size_t key_len;\n"
             "    if (!enif_inspect_binary(env, x, &xb))\n"
             "        return 0;\n"
             "    key_len = strlen(key_to_check_for);\n"
             "    return (xb.size == key_len &&\n"
             "            memcmp(xb.data, key_to_check_for, key_len) == 0);\n"
             "}\n"];
       Maps ->
            ["static int is_key(ErlNifEnv *env,\n"
             "                  const ERL_NIF_TERM x,\n"
             "                  const ERL_NIF_TERM key_to_check_for)\n",
             "{\n",
             "    return enif_is_identical(x, key_to_check_for);\n"
             "}\n"];
       true ->
            ""
    end.

format_nif_cc_mk_mk_key(_Mod, _Defs, _AnRes, Opts) ->
    Maps = gpb_lib:get_records_or_maps_by_opts(Opts) == maps,
    MapsKeyType = gpb_lib:get_maps_key_type_by_opts(Opts),
    if Maps, MapsKeyType == binary ->
            ["static ERL_NIF_TERM mk_key(ErlNifEnv *env,\n"
             "                           const char *key)\n",
             "{\n",
             "    ERL_NIF_TERM   b;\n"
             "    unsigned char *data;\n"
             "    size_t         len;\n"
             "\n"
             "    len = strlen(key);\n"
             "    data = enif_make_new_binary(env, len, &b);\n"
             "    memmove(data, key, len);\n"
             "    return b;\n"
             "}\n"];
       Maps ->
            ["static ERL_NIF_TERM mk_key(ErlNifEnv *env,\n"
             "                           const ERL_NIF_TERM key)\n",
             "{\n",
             "    return key;\n"
             "}\n"];
       true ->
            ""
    end.

format_nif_cc_utf8_conversion(_Mod, _Defs, AnRes, Opts) ->
    case is_any_field_of_type_string(AnRes) of
        true  -> format_nif_cc_utf8_conversion_code(Opts);
        false -> ""
    end.

is_any_field_of_type_string(#anres{used_types=UsedTypes}) ->
    sets:is_element(string, UsedTypes).

is_any_field_of_type_enum(#anres{used_types=UsedTypes}) ->
    sets:fold(fun({enum,_}, _) -> true;
                 (_, Acc) -> Acc
              end,
              false,
              UsedTypes).

is_any_field_of_type_bool(#anres{used_types=UsedTypes}) ->
    sets:is_element(bool, UsedTypes).

is_any_field_of_type_float_or_double(#anres{used_types=UsedTypes}) ->
    sets:is_element(float, UsedTypes) orelse
        sets:is_element(double, UsedTypes).

format_nif_cc_utf8_conversion_code(Opts) ->
    [case gpb_lib:get_strings_as_binaries_by_opts(Opts) of
         true ->
             ["static ERL_NIF_TERM\n",
              "utf8_to_erl_string(ErlNifEnv *env,\n",
              "                   const char *utf8data,\n",
              "                   unsigned int numOctets)\n"
              "{\n",
              "    ERL_NIF_TERM   b;\n",
              "    unsigned char *data;\n",
              "\n",
              "    data = enif_make_new_binary(env, numOctets, &b);\n",
              "    memmove(data, utf8data, numOctets);\n",
              "    return b;\n",
              "}\n"];
         false ->
             ["/* Source for info is https://www.ietf.org/rfc/rfc2279.txt */\n",
              "\n",
              "static int\n",
              "utf8_count_codepoints(const char *sinit, int len)\n",
              "{\n",
              "    int n = 0;\n",
              "    const unsigned char *s0 = (unsigned char *)sinit;\n",
              "    const unsigned char *s  = s0;\n",
              "\n",
              "    while ((s - s0) < len)\n",
              "    {\n",
              "        if (*s <= 0x7f) { n++; s++; } /* code point fits 1 octet */\n",
              "        else if (*s <= 0xdf) { n++; s += 2; } /* 2 octets */\n",
              "        else if (*s <= 0xef) { n++; s += 3; } /* 3 octets */\n",
              "        else if (*s <= 0xf7) { n++; s += 4; }\n",
              "        else if (*s <= 0xfb) { n++; s += 5; }\n",
              "        else if (*s <= 0xfd) { n++; s += 6; }\n",
              "        else return -1;\n",
              "\n",
              "        if ((s - s0) > len)\n",
              "            return -1;\n",
              "    }\n",
              "    return n;\n",
              "}\n",
              "\n",
              "static int\n",
              "utf8_to_uint32(unsigned int *dest, const char *src,\n",
              "               int numCodePoints)\n",
              "{\n",
              "    int i;\n",
              "    const unsigned char *s = (unsigned char *)src;\n",
              "\n",
              "\n",
              "    /* Should perhaps check for illegal chars in d800-dfff and\n",
              "     * other illegal chars\n",
              "     */\n",
              "\n",
              "    for (i = 0; i < numCodePoints; i++)\n",
              "    {\n",
              "        if (*s <= 0x7f)\n",
              "            *dest++ = *s++;\n",
              "        else if (*s <= 0xdf) /* code point is 2 octets long */\n",
              "        {\n",
              "            *dest   =  *s++ & 0x1f; *dest <<= 6;\n",
              "            *dest++ |= *s++ & 0x3f;\n",
              "        }\n",
              "        else if (*s <= 0xef) /* code point is 3 octets long */\n",
              "        {\n",
              "            *dest   =  *s++ & 0x0f; *dest <<= 6;\n",
              "            *dest   |= *s++ & 0x3f; *dest <<= 6;\n",
              "            *dest++ |= *s++ & 0x3f;\n",
              "        }\n",
              "        else if (*s <= 0xf7) /* code point is 4 octets long */\n",
              "        {\n",
              "            *dest   =  *s++ & 0x07; *dest <<= 6;\n",
              "            *dest   |= *s++ & 0x3f; *dest <<= 6;\n",
              "            *dest   |= *s++ & 0x3f; *dest <<= 6;\n",
              "            *dest++ |= *s++ & 0x3f;\n",
              "        }\n",
              "        else if (*s <= 0xfb) /* code point is 5 octets long */\n",
              "        {\n",
              "            *dest   =  *s++ & 0x03; *dest <<= 6;\n",
              "            *dest   |= *s++ & 0x3f; *dest <<= 6;\n",
              "            *dest   |= *s++ & 0x3f; *dest <<= 6;\n",
              "            *dest   |= *s++ & 0x3f; *dest <<= 6;\n",
              "            *dest++ |= *s++ & 0x3f;\n",
              "        }\n",
              "        else if (*s <= 0xfd) /* code point is 6 octets long */\n",
              "        {\n",
              "            *dest   =  *s++ & 0x01; *dest <<= 6;\n",
              "            *dest   |= *s++ & 0x3f; *dest <<= 6;\n",
              "            *dest   |= *s++ & 0x3f; *dest <<= 6;\n",
              "            *dest   |= *s++ & 0x3f; *dest <<= 6;\n",
              "            *dest   |= *s++ & 0x3f; *dest <<= 6;\n",
              "            *dest++ |= *s++ & 0x3f;\n",
              "        }\n",
              "        else\n",
              "            return 0;\n",
              "    }\n",
              "    return 1;\n",
              "}\n",
              "\n",
              "static ERL_NIF_TERM\n",
              "utf8_to_erl_string(ErlNifEnv *env,\n",
              "                   const char *utf8data,\n",
              "                   unsigned int numOctets)\n",
              "{\n",
              "    int numcp = utf8_count_codepoints(utf8data, numOctets);\n",
              "\n",
              "    if (numcp < 0)\n",
              "    {\n",
              "        return enif_make_string(env,\n",
              "                                \"<invalid UTF-8>\",\n",
              "                                ERL_NIF_LATIN1);\n",
              "    }\n",
              "    else\n",
              "    {\n",
              "        unsigned int  cp[numcp];\n",
              "        ERL_NIF_TERM  es[numcp];\n",
              "        int i;\n",
              "\n",
              "        utf8_to_uint32(cp, utf8data, numcp);\n",
              "        for (i = 0; i < numcp; i++)\n",
              "            es[i] = enif_make_uint(env, cp[i]);\n",
              "        return enif_make_list_from_array(env, es, numcp);\n"
              "    }\n",
              "}\n"]
     end,
     "\n",
     case gpb_lib:get_strings_as_binaries_by_opts(Opts) of
         true ->
             "";
         false ->
             ["static int\n",
              "utf8_count_octets(ErlNifEnv *env, ERL_NIF_TERM str)\n",
              "{\n",
              "    int n = 0;\n",
              "\n",
              "    while (!enif_is_empty_list(env, str))\n",
              "    {\n",
              "        ERL_NIF_TERM head, tail;\n",
              "        unsigned int c;\n",
              "\n",
              "        if (!enif_get_list_cell(env, str, &head, &tail))\n",
              "            return -1;\n",
              "        if (!enif_get_uint(env, head, &c))\n",
              "            return -1;\n",
              "\n",
              "        if (c <= 0x7f) n += 1;\n",
              "        else if (c <= 0x7ff) n += 2;\n",
              "        else if (c <= 0xffff) n += 3;\n",
              "        else if (c <= 0x1Fffff) n += 4;\n",
              "        else if (c <= 0x3FFffff) n += 5;\n",
              "        else if (c <= 0x7FFFffff) n += 6;\n",
              "        else return -1;\n",
              "\n",
              "        str = tail;\n",
              "    }\n",
              "    return n;\n",
              "}\n",
              "\n",
              "static int\n",
              "utf8_to_octets(ErlNifEnv *env, ERL_NIF_TERM str, char *dest)\n",
              "{\n",
              "    unsigned char *s = (unsigned char *)dest;\n",
              "\n",
              "    while (!enif_is_empty_list(env, str))\n",
              "    {\n",
              "        ERL_NIF_TERM head, tail;\n",
              "        unsigned int c;\n",
              "\n",
              "        if (!enif_get_list_cell(env, str, &head, &tail))\n",
              "            return -1;\n",
              "        if (!enif_get_uint(env, head, &c))\n",
              "            return -1;\n",
              "\n",
              "        if (c <= 0x7f)\n",
              "            *s++ = c;\n",
              "        else if (c <= 0x7ff)\n",
              "        {\n",
              "            *s++ = 0xc0 | (c >> 6);\n",
              "            *s++ = 0x80 | (c & 0x3f);\n",
              "        }\n",
              "        else if (c <= 0xffff)\n",
              "        {\n",
              "            *s++ = 0xe0 | (c >> 12);\n",
              "            *s++ = 0x80 | ((c >> 6) & 0x3f);\n",
              "            *s++ = 0x80 | (c        & 0x3f);\n",
              "        }\n",
              "        else if (c <= 0x1Fffff)\n",
              "        {\n",
              "            *s++ = 0xf0 | (c >> 18);\n",
              "            *s++ = 0x80 | ((c >> 12) & 0x3f);\n",
              "            *s++ = 0x80 | ((c >>  6) & 0x3f);\n",
              "            *s++ = 0x80 | (c         & 0x3f);\n",
              "        }\n",
              "        else if (c <= 0x3FFffff)\n",
              "        {\n",
              "            *s++ = 0xf0 | (c >> 24);\n",
              "            *s++ = 0x80 | ((c >> 18) & 0x3f);\n",
              "            *s++ = 0x80 | ((c >> 12) & 0x3f);\n",
              "            *s++ = 0x80 | ((c >>  6) & 0x3f);\n",
              "            *s++ = 0x80 | (c         & 0x3f);\n",
              "        }\n",
              "        else if (c <= 0x7FFFffff)\n",
              "        {\n",
              "            *s++ = 0xf0 | (c >> 30);\n",
              "            *s++ = 0x80 | ((c >> 24) & 0x3f);\n",
              "            *s++ = 0x80 | ((c >> 18) & 0x3f);\n",
              "            *s++ = 0x80 | ((c >> 12) & 0x3f);\n",
              "            *s++ = 0x80 | ((c >>  6) & 0x3f);\n",
              "            *s++ = 0x80 | (c         & 0x3f);\n",
              "        }\n",
              "        else\n",
              "            return 0;\n",
              "\n",
              "        str = tail;\n",
              "    }\n",
              "    return 1;\n"
              "}\n"]
     end,
     "\n"].

format_nif_cc_unknown_pack_unpack(#anres{unknowns_info=UnknownsInfo}) ->
    case UnknownsInfo of
        both_with_and_without_field_for_unknowns ->
            format_nif_cc_unknown_pack_unpack_aux();
        all_are_with_field_for_unknowns ->
            format_nif_cc_unknown_pack_unpack_aux();
        all_are_without_field_for_unknowns ->
            "";
        no_msgs ->
            ""
    end.

format_nif_cc_unknown_pack_unpack_aux() ->
    ["static int\n"
     "pack_unknown_fields(ErlNifEnv *env,\n"
     "                    ::google::protobuf::UnknownFieldSet *ufset,\n"
     "                    ERL_NIF_TERM unknowns)\n"
     "{\n"
     "    while (!enif_is_empty_list(env, unknowns))\n"
     "    {\n"
     "        ERL_NIF_TERM head, tail;\n"
     "        if (!enif_get_list_cell(env, unknowns, &head, &tail))\n"
     "            return 0;\n"
     "\n"
     "        int arity;\n"
     "        const ERL_NIF_TERM *tuple;\n"
     "        \n"
     "\n"
     "        if (!enif_get_tuple(env, head, &arity, &tuple))\n"
     "            return 0;\n"
     "        if (arity != 3)\n"
     "            return 0;\n"
     "\n"
     "        ErlNifUInt64 fnum;\n"
     "        if (!enif_get_uint64(env, tuple[1], &fnum))\n"
     "            return 0;\n"
     "\n"
     "        if (enif_is_identical(gpb_ua_varint, tuple[0]))\n"
     "        {\n"
     "            ErlNifUInt64 v;\n"
     "            if (!enif_get_uint64(env, tuple[2], &v))\n"
     "                return 0;\n"
     "            ufset->AddVarint(fnum, v);\n"
     "        }\n"
     "        else if (enif_is_identical(gpb_ua_fixed64, tuple[0]))\n"
     "        {\n"
     "            ErlNifUInt64 v;\n"
     "            if (!enif_get_uint64(env, tuple[2], &v))\n"
     "                return 0;\n"
     "            ufset->AddFixed64(fnum, v);\n"
     "        }\n"
     "        else if (enif_is_identical(gpb_ua_length_delimited, tuple[0])\n"
     "                 && arity == 3)\n"
     "        {\n"
     "            ErlNifBinary bin;\n"
     "            ::std::string *s;\n"
     "            if (!enif_inspect_binary(env, tuple[2], &bin))\n"
     "                return 0;\n"
     "            s = ufset->AddLengthDelimited(fnum);\n"
     "            s->insert(0, (const char *)bin.data, (size_t)bin.size);\n"
     "        }\n"
     "        else if (enif_is_identical(gpb_ua_group, tuple[0])\n"
     "                 && arity == 3)\n"
     "        {\n"
     "            if (!enif_is_list(env, tuple[2]))\n"
     "                return 0;            \n"
     "            ::google::protobuf::UnknownFieldSet *set2 = \n"
     "                ufset->AddGroup(fnum);\n"
     "            if (!pack_unknown_fields(env, set2, tuple[2]))\n"
     "                return 0;\n"
     "        }\n"
     "        else if (enif_is_identical(gpb_ua_fixed32, tuple[0]))\n"
     "        {\n"
     "            unsigned int v;\n"
     "            if (!enif_get_uint(env, tuple[2], &v))\n"
     "                return 0;\n"
     "            ufset->AddFixed32(fnum, v);\n"
     "        }\n"
     "        else\n"
     "            return 0;\n"
     "\n"
     "        unknowns = tail;\n"
     "    }\n"
     "    return 1;\n"
     "}\n"
     "\n"
     "static int\n"
     "pack_unknown_msg_fields(ErlNifEnv *env,\n"
     "                        ::google::protobuf::Message *m,\n"
     "                        ERL_NIF_TERM unknowns)\n"
     "{\n"
     "    const ::google::protobuf::Reflection *r = m->GetReflection();\n"
     "    ::google::protobuf::UnknownFieldSet *ufset = \n"
     "        r->MutableUnknownFields(m);\n"
     "    return pack_unknown_fields(env, ufset, unknowns);\n"
     "}\n"
     "\n"
     "/* Return an Erlang list of unknowns\n"
     " * Each unknown is:\n"
     " *   {varint, FNum, N} |\n"
     " *   {fixed, FNum, 32, N} |\n"
     " *   {fixed, FNum, 64, N} |\n"
     " *   {length_delimited, FNum, <<...>>} |\n"
     " *   {group, FNum, [unknown()]}\n"
     " * when FNum :: non_neg_integer()  (32 bits)\n"
     " *      N    :: non_neg_integer()  (32 | 64 bits)\n"
     " */\n"
     "static ERL_NIF_TERM\n"
     "unpack_unknown_fields(ErlNifEnv *env,\n"
     "                      const ::google::protobuf::UnknownFieldSet &fields)\n"
     "{\n"
     "    int num_fields = fields.field_count();\n"
     "    ERL_NIF_TERM uelem[num_fields];\n"
     "    int i;\n"
     "\n"
     "    for (i = 0; i < num_fields; i++)\n"
     "    {\n"
     "        const ::google::protobuf::UnknownField &f = fields.field(i);\n"
     "        switch (f.type())\n"
     "        {\n"
     "            case ::google::protobuf::UnknownField::Type::TYPE_VARINT:\n"
     "                {\n"
     "                    ERL_NIF_TERM fnum = enif_make_uint(\n"
     "                                          env, f.number());\n"
     "                    ERL_NIF_TERM n = enif_make_uint64(\n"
     "                                          env, f.varint());\n"
     "                    \n"
     "                    uelem[i] = enif_make_tuple3(\n"
     "                                 env, gpb_ua_varint, fnum, n);\n"
     "                }\n"
     "                break;\n"
     "            case ::google::protobuf::UnknownField::Type::TYPE_FIXED64:\n"
     "                {\n"
     "                    ERL_NIF_TERM fnum = enif_make_uint(\n"
     "                                          env, f.number());\n"
     "                    ERL_NIF_TERM n = enif_make_uint64(\n"
     "                                       env, f.fixed64());\n"
     "                    uelem[i] = enif_make_tuple3(\n"
     "                                 env, gpb_ua_fixed64, fnum, n);\n"
     "                }\n"
     "                break;\n"
     "            case ::google::protobuf::UnknownField::Type::TYPE_LENGTH_DELIMITED:\n"
     "                {\n"
     "                    ERL_NIF_TERM fnum = enif_make_uint(\n"
     "                                          env, f.number());\n"
     "                    const ::std::string &f_s = f.length_delimited();\n"
     "                    unsigned char *b_s;\n"
     "                    ERL_NIF_TERM bin;\n"
     "                    BYTE_SIZE_TYPE byteSize = f_s.length();\n"
     "                    b_s = enif_make_new_binary(env, byteSize, &bin);\n"
     "                    memmove(b_s, f_s.data(), byteSize);\n"
     "                    uelem[i] = enif_make_tuple3(\n"
     "                                  env,\n"
     "                                  gpb_ua_length_delimited,\n"
     "                                  fnum, bin);\n"
     "                }\n"
     "                break;\n"
     "            case ::google::protobuf::UnknownField::Type::TYPE_GROUP:\n"
     "                {\n"
     "                    ERL_NIF_TERM fnum = enif_make_uint(\n"
     "                                          env, f.number());\n"
     "                    const ::google::protobuf::UnknownFieldSet &g = \n"
     "                              f.group();\n"
     "                    ERL_NIF_TERM u = unpack_unknown_fields(env, g);\n"
     "                    uelem[i] = enif_make_tuple3(\n"
     "                                 env, gpb_ua_group, fnum, u);\n"
     "                }\n"
     "                break;\n"
     "            case ::google::protobuf::UnknownField::Type::TYPE_FIXED32:\n"
     "                {\n"
     "                    ERL_NIF_TERM fnum = enif_make_uint(\n"
     "                                          env, f.number());\n"
     "                    ERL_NIF_TERM n = enif_make_uint(\n"
     "                                       env, f.fixed32());\n"
     "                    uelem[i] = enif_make_tuple3(\n"
     "                                  env, gpb_ua_fixed32, fnum, n);\n"
     "                }\n"
     "                break;\n"
     "            default:\n"
     "                break;\n"
     "        }\n"
     "    }\n"
     "    return enif_make_list_from_array(env, uelem, num_fields);\n"
     "}\n"
     "\n"
     "static ERL_NIF_TERM\n"
     "unpack_unknown_msg_fields(ErlNifEnv *env,\n"
     "                          const ::google::protobuf::Message *m)\n"
     "{\n"
     "    const ::google::protobuf::Reflection *r = m->GetReflection();\n"
     "    const ::google::protobuf::UnknownFieldSet &fields =\n"
     "              r->GetUnknownFields(*m);\n"
     "    return unpack_unknown_fields(env, fields);\n"
     "}\n"].

format_nif_cc_foot(Mod, Defs, Opts) ->
    ["static int\n",
     "load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info)\n",
     "{\n",
     "    install_consts(env);\n"
     "    install_atoms(env);\n"
     "    return 0;\n",
     "}\n",
     "\n",
     "static int\n",
     "reload(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info)\n",
     "{\n",
     "    return 0;\n",
     "}\n",
     "\n",
     "void\n",
     "unload(ErlNifEnv *env, void *priv_data)\n",
     "{\n",
     "}\n",
     "\n",
     "static int\n",
     "upgrade(ErlNifEnv *env, void **priv_data, void **old_priv_data,\n",
     "        ERL_NIF_TERM load_info)\n",
     "{\n",
     "    return 0;\n",
     "}\n",
     "\n",
     "static ErlNifFunc nif_funcs[] =\n",
     "{\n",
     %% Dirty schedulers flags appeared in Erlang 17.3 = enif 2.7
     %% but only if Erlang was configured with --enable-dirty-schedulers
     "#if ", format_nif_check_version_or_later(2, 7), "\n"
     "#ifdef ERL_NIF_DIRTY_SCHEDULER_SUPPORT\n",
     format_nif_cc_nif_funcs_list(Defs, "ERL_NIF_DIRTY_JOB_CPU_BOUND, ", Opts),
     "#else /* ERL_NIF_DIRTY_SCHEDULER_SUPPORT */\n",
     format_nif_cc_nif_funcs_list(Defs, "", Opts),
     "#endif /* ERL_NIF_DIRTY_SCHEDULER_SUPPORT */\n",
     "#else /* before 2.7 or 17.3 */\n",
     format_nif_cc_nif_funcs_list(Defs, no_flags, Opts),
     "#endif /* before 2.7 or 17.3 */\n"
     "};\n",
     "\n",
     ?f("ERL_NIF_INIT(~s, nif_funcs, load, reload, upgrade, unload)\n",
        [Mod])].

format_nif_check_version_or_later(Major, Minor) ->
    ?f("ERL_NIF_MAJOR_VERSION > ~w"
       " || "
       "(ERL_NIF_MAJOR_VERSION == ~w && ERL_NIF_MINOR_VERSION >= ~w)",
       [Major, Major, Minor]).

format_nif_cc_nif_funcs_list(Defs, Flags, Opts) ->
    DoJson = gpb_lib:json_by_opts(Opts),
    MsgNames = [MsgName || {{msg, MsgName}, _MsgFields} <- Defs],
    FlagStr = if Flags == no_flags -> "";
                 true -> ", " ++ Flags
              end,
    [begin
         EncodeFnName = gpb_lib:mk_fn(encode_msg_, MsgName),
         EncodeCFnName = mk_c_fn(encode_msg_, MsgName),
         DecodeFnName = gpb_lib:mk_fn(decode_msg_, MsgName),
         DecodeCFnName = mk_c_fn(decode_msg_, MsgName),
         ToJsonFnName = gpb_lib:mk_fn(to_json_msg_, MsgName),
         ToJsonCFnName = mk_c_fn(to_json_msg_, MsgName),
         FromJsonFnName = gpb_lib:mk_fn(from_json_msg_, MsgName),
         FromJsonCFnName = mk_c_fn(from_json_msg_, MsgName),
         IsLast = I == length(MsgNames),
         Comma = ["," || not IsLast],
         DComma = if DoJson     -> ",";
                     not DoJson -> Comma
                  end,
         [?f(  "    {\"~s\", 1, ~s~s},\n",
               [EncodeFnName, EncodeCFnName, FlagStr]),
          ?f(  "    {\"~s\", 1, ~s~s}~s\n",
               [DecodeFnName, DecodeCFnName, FlagStr, DComma]),
          [[?f("    {\"~s\", 1, ~s~s},\n",
               [ToJsonFnName, ToJsonCFnName, FlagStr]),
            ?f("    {\"~s\", 1, ~s~s}~s\n",
               [FromJsonFnName, FromJsonCFnName, FlagStr, Comma])]
           || DoJson]]

     end
     || {I, MsgName} <- gpb_lib:index_seq(MsgNames)].

format_nif_cc_encoders(Mod, Defs, CCMapping, Opts) ->
    [format_nif_cc_encoder(Mod, MsgName, Fields, CCMapping, Opts)
     || {{msg, MsgName}, Fields} <- Defs].

format_nif_cc_encoder(_Mod, MsgName, _Fields, CCMapping, _Opts) ->
    FnName = mk_c_fn(encode_msg_, MsgName),
    PackFnName = mk_c_fn(p_msg_, MsgName),
    #cc_msg{type=CMsgType} = dict:fetch(MsgName, CCMapping),
    ["static ERL_NIF_TERM\n",
     FnName,"(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])\n",
     "{\n",
     "    ErlNifBinary data;\n",
     "    BYTE_SIZE_TYPE byteSize;\n",
     "    ",CMsgType," *m = new ",CMsgType,"();\n\n",
     ""
     "    if (argc != 1)\n"
     "    {\n"
     "        delete m;\n"
     "        return enif_make_badarg(env);\n"
     "    }\n\n"
     ""
     "    if (m == NULL)\n"
     "    {\n"
     "        delete m;\n"
     "        return enif_make_badarg(env);\n"
     "    }\n\n"
     ""
     "    if (!",PackFnName,"(env, argv[0], m))\n"
     "    {\n"
     "        delete m;\n"
     "        return enif_make_badarg(env);\n"
     "    }\n\n"
     ""
     "    byteSize = m->BYTE_SIZE_METHOD();\n"
     "    if (!enif_alloc_binary(byteSize, &data))\n"
     "    {\n"
     "        delete m;\n"
     "        return enif_make_badarg(env);\n"
     "    }\n\n"
     ""
     "    if (!m->SerializeToArray(data.data, byteSize))\n"
     "    {\n"
     "        delete m;\n"
     "        return enif_make_badarg(env);\n"
     "    }\n\n"
     ""
     "    delete m;\n"
     "    return enif_make_binary(env, &data);\n"
     "}\n"
     "\n"].

format_nif_cc_packers(_Mod, Defs, CCMapping, Opts) ->
    [format_nif_cc_packer(MsgName, Fields, Defs, CCMapping, Opts)
     || {_msg_or_group, MsgName, Fields} <- gpb_lib:msgs_or_groups(Defs)].

format_nif_cc_packer(MsgName, MsgFields, Defs, CCMapping, Opts) ->
    Maps = gpb_lib:get_records_or_maps_by_opts(Opts) == maps,
    Mapping = gpb_lib:get_mapping_and_unset_by_opts(Opts),
    Fields = case Mapping of
                  #maps{unset_optional=omitted, oneof=flat} ->
                      gpb_lib:flatten_oneof_fields(MsgFields);
                  _ ->
                      MsgFields
              end,
    PackFnName = mk_c_fn(p_msg_, MsgName),
    #cc_msg{type=CMsgType} = dict:fetch(MsgName, CCMapping),
    ["static int\n",
     PackFnName,["(ErlNifEnv *env, ",
                 "const ERL_NIF_TERM r,",
                 " ",CMsgType," *m)\n"],
     "{\n",
     if Maps ->
             NFieldsPlus1 = integer_to_list(length(MsgFields)+1),
             ["    ERL_NIF_TERM k, v;\n",
              "    ErlNifMapIterator iter;\n",
              "    ERL_NIF_TERM elem[",NFieldsPlus1,"];\n",
              "    ErlNifMapIteratorEntry first;\n",
              "    int i;\n\n",
              "",
              initialize_map_iterator(4, "first"),
              "    for (i = 1; i < ",NFieldsPlus1,"; i++)\n",
              "        elem[i] = gpb_x_no_value;\n\n",
              ""
              "    if (!enif_map_iterator_create(env, r, &iter, first))\n",
              "        return 0;\n\n",
              ""
              "    while (enif_map_iterator_get_pair(env, &iter, &k, &v))\n",
              "    {\n",
              gpb_lib:split_indent_iolist(
                8,
                [begin
                     ElemIndex = gpb_lib:get_field_rnum(Field)-1,
                     SrcVar = ?f("elem[~w]",[ElemIndex]),
                     ?f("~sif (is_key(env, k, ~s))\n"
                        "{\n"
                        "    elem[~w] = v;\n"
                        "~s\n"
                        "}\n",
                        [if I == 1 -> "";
                            I >  1 -> "else "
                         end,
                         mk_c_field_var(gpb_lib:get_field_name(Field)),
                         ElemIndex,
                         gpb_lib:split_indent_iolist(
                           4,
                           format_nif_cc_field_packer(
                             SrcVar, "m",
                             case Mapping of
                                 #maps{unset_optional=omitted} ->
                                     %% No need anymore to check for presence
                                     %% int the field packer. We've done that.
                                     optional_to_mandatory(Field);
                                 #maps{unset_optional=present_undefined} ->
                                     Field
                             end,
                             Defs, CCMapping, Opts))])
                 end
                 || {I, Field} <- gpb_lib:index_seq(Fields)]),
              "        enif_map_iterator_next(env, &iter);\n",
              "    }\n",
              "    enif_map_iterator_destroy(env, &iter);\n",
              "\n"];
        not Maps ->
             ["    int arity;\n"
              "    const ERL_NIF_TERM *elem;\n\n"
              ""
              "    if (!enif_get_tuple(env, r, &arity, &elem))\n"
              "        return 0;\n"
              "\n",
              ?f("    if (arity != ~w)\n"
                 "        return 0;\n",
                 [length(Fields) + 1]),
              "\n",
              [begin
                   SrcVar = ?f("elem[~w]",[I]),
                   gpb_lib:split_indent_iolist(
                     4,
                     format_nif_cc_field_packer(SrcVar, "m", Field,
                                                Defs, CCMapping, Opts))
               end
               || {I, Field} <- gpb_lib:index_seq(Fields)]]
     end,
     "\n"
     "    return 1;\n"
     "}\n",
     "\n"].

optional_to_mandatory(#?gpb_field{occurrence=Occurrence}=Field) ->
    case Occurrence of
        optional -> Field#?gpb_field{occurrence=required};
        defaulty -> Field#?gpb_field{occurrence=required};
        required -> Field;
        repeated -> Field
    end;
optional_to_mandatory(#gpb_oneof{}=Field) ->
    Field.

format_nif_cc_field_packer(SrcVar, MsgVar, #?gpb_field{}=Field,
                           Defs, CCMapping, Opts) ->
    case categorize_field_kind(Field) of
        required ->
            format_nif_cc_field_packer_single(SrcVar, MsgVar, Field,
                                              Defs, CCMapping, Opts, set);
        optional ->
            format_nif_cc_field_packer_optional(SrcVar, MsgVar, Field,
                                                Defs, CCMapping, Opts);
        defaulty ->
            format_nif_cc_field_packer_optional(SrcVar, MsgVar, Field,
                                                Defs, CCMapping, Opts);
        repeated ->
            format_nif_cc_field_packer_repeated(SrcVar, MsgVar, Field,
                                                Defs, CCMapping, Opts);
        map ->
            format_nif_cc_field_packer_maptype(SrcVar, MsgVar, Field,
                                               Defs, CCMapping, Opts);
        unknowns ->
            format_nif_cc_field_packer_unknowns(SrcVar, MsgVar)
    end;
format_nif_cc_field_packer(SrcVar, MsgVar, #gpb_oneof{}=Field,
                           Defs, CCMapping, Opts) ->
    #gpb_oneof{fields=OFields} = Field,
    [?f("if (!enif_is_identical(~s, gpb_x_no_value))~n"
        "{~n"
        "    int oarity;~n"
        "    const ERL_NIF_TERM *oelem;~n~n"
        ""
        "    if (!enif_get_tuple(env, ~s, &oarity, &oelem) || oarity != 2)~n"
        "        return 0;~n~n"
        ""
        "    ~s~n"
        "}~n",
        [SrcVar, SrcVar,
         gpb_lib:split_indent_butfirst_iolist(
           4,
           format_nif_cc_oneof_packer("oelem[0]", "oelem[1]",
                                      MsgVar, OFields,
                                      Defs, CCMapping, Opts))]),
     "\n"].

format_nif_cc_oneof_packer(NameVar, SrcVar, MsgVar, OFields,
                           Defs, CCMapping, Opts) ->
    [[begin
          Else = if I == 1 -> "";
                    I >  1 -> "else "
                 end,
          AtomVar = mk_c_var(gpb_aa_, Name),
          [?f("~sif (enif_is_identical(~s, ~s))~n", [Else, NameVar, AtomVar]),
           split_indent_iolist_unless_curly_block(
             4,
             format_nif_cc_field_packer_single(SrcVar, MsgVar, OField,
                                               Defs, CCMapping, Opts, set))]
      end
      || {I, #?gpb_field{name=Name}=OField} <- gpb_lib:index_seq(OFields)],
     "else\n"
     "    return 0;\n"].

format_nif_cc_field_packer_optional(SrcVar, MsgVar, Field,
                                    Defs, CCMapping, Opts) ->
    [?f("if (!enif_is_identical(~s, gpb_x_no_value))\n", [SrcVar]),
     format_nif_cc_field_packer_single(SrcVar, MsgVar, Field,
                                       Defs, CCMapping, Opts, set)].

format_nif_cc_field_packer_single(SrcVar, MsgVar,
                                  #?gpb_field{type={group,Name}}=Field,
                                  Defs, CCMapping, Opts, Setter) ->
    format_nif_cc_field_packer_single(
      SrcVar, MsgVar,
      Field#?gpb_field{type={msg,Name}},
      Defs, CCMapping, Opts, Setter);
format_nif_cc_field_packer_single(SrcVar, MsgVar, Field, Defs, CCMapping,
                                  Opts, Setter) ->
    #?gpb_field{name=FName, type=FType} = Field,
    CxxFName = 'field_name_to_c++'(FName),
    SetFn = fun(Exprs) ->
                    case Setter of
                        set ->
                            ?f("~s->set_~s(~s);",
                               [MsgVar, CxxFName, gpb_lib:comma_join(Exprs)]);
                        add ->
                            ?f("~s->add_~s(~s);",
                               [MsgVar, CxxFName, gpb_lib:comma_join(Exprs)]);
                        {set_var, V} ->
                            case Exprs of
                                [Val] -> ?f("~s = ~s;", [V, Val]);
                                [S,N] -> ?f("~s.assign(~s, ~s);", [V, S, N])
                            end
                    end
            end,
    case FType of
        float ->
            ?f("{\n"
               "    double v;\n"
               "    if (enif_is_identical(~s, gpb_aa_infinity))\n"
               "        v = INFINITY;\n"
               "    else if (enif_is_identical(~s, gpb_aa_minfinity))\n"
               "        v = -INFINITY;\n"
               "    else if (enif_is_identical(~s, gpb_aa_nan))\n"
               "        v = NAN;\n"
               "    else if (!enif_get_double(env, ~s, &v))\n"
               "        return 0;\n"
               "    ~s\n"
               "}\n",
               [SrcVar, SrcVar, SrcVar, SrcVar, SetFn(["(float)v"])]);
        double ->
            ?f("{\n"
               "    double v;\n"
               "    if (enif_is_identical(~s, gpb_aa_infinity))\n"
               "        v = INFINITY;\n"
               "    else if (enif_is_identical(~s, gpb_aa_minfinity))\n"
               "        v = -INFINITY;\n"
               "    else if (enif_is_identical(~s, gpb_aa_nan))\n"
               "        v = NAN;\n"
               "    else if (!enif_get_double(env, ~s, &v))\n"
               "        return 0;\n"
               "    ~s\n"
               "}\n",
               [SrcVar, SrcVar, SrcVar, SrcVar, SetFn(["v"])]);
        _S32 when FType == sint32;
                  FType == int32;
                  FType == sfixed32 ->
            ?f("{\n"
               "    int v;\n"
               "    if (!enif_get_int(env, ~s, &v))\n"
               "        return 0;\n"
               "    ~s\n"
               "}\n",
               [SrcVar, SetFn(["v"])]);
        _S64 when FType == sint64;
                  FType == int64;
                  FType == sfixed64 ->
            ?f("{\n"
               "    ErlNifSInt64 v;\n"
               "    if (!enif_get_int64(env, ~s, &v))\n"
               "        return 0;\n"
               "    ~s\n"
               "}\n",
               [SrcVar, SetFn(["v"])]);
        _U32 when FType == uint32;
                  FType == fixed32 ->
            ?f("{\n"
               "    unsigned int v;\n"
               "    if (!enif_get_uint(env, ~s, &v))\n"
               "        return 0;\n"
               "    ~s\n"
               "}\n",
               [SrcVar, SetFn(["v"])]);
        _U64 when FType == uint64;
                  FType == fixed64 ->
            ?f("{\n"
               "    ErlNifUInt64 v;\n"
               "    if (!enif_get_uint64(env, ~s, &v))\n"
               "        return 0;\n"
               "    ~s\n"
               "}\n",
               [SrcVar, SetFn(["v"])]);
        bool ->
            ?f("{\n"
               "    if (enif_is_identical(~s, gpb_aa_true))\n"
               "        ~s\n"
               "    else if (enif_is_identical(~s, gpb_true_int))\n"
               "        ~s\n"
               "    else\n"
               "        ~s\n"
               "}\n",
               [SrcVar, SetFn(["1"]), SrcVar, SetFn(["1"]), SetFn(["0"])]);
        {enum, EnumName} ->
            #cc_enum{type=EType,
                     enums=CCEnums} = dict:fetch(EnumName, CCMapping),
            ["{\n",
             ?f("    int v;\n"
                "    if (enif_get_int(env, ~s, &v))\n"
                "        ~s\n",
                [SrcVar, SetFn([?f("(~s)v", [EType])])]),
             [?f("    else if (enif_is_identical(~s, ~s))\n"
                 "        ~s\n",
                 [SrcVar, mk_c_var(gpb_aa_, Sym),
                  SetFn([CCSym])])
              || {Sym, CCSym} <- CCEnums],
             "    else\n"
             "        return 0;\n"
             "}\n"];
        string ->
            case gpb_lib:get_strings_as_binaries_by_opts(Opts) of
                true ->
                    ?f("{\n"
                       "    ErlNifBinary b;\n"
                       "    if (!enif_inspect_binary(env, ~s, &b))\n"
                       "        return 0;\n"
                       "    ~s\n"
                       "}\n",
                       [SrcVar,
                        SetFn(["reinterpret_cast<char *>(b.data)",
                               "b.size"])]);
                false ->
                    ?f("{\n"
                       "    size_t num_octs = utf8_count_octets(env, ~s);\n"
                       "\n"
                       "    if (num_octs < 0)\n"
                       "        return 0;\n"
                       "    else\n"
                       "    {\n"
                       "         char s[num_octs];\n"
                       "         utf8_to_octets(env, ~s, s);\n"
                       "         ~s\n"
                       "    }\n"
                       "}\n",
                       [SrcVar, SrcVar, SetFn(["s", "num_octs"])])
            end;
        bytes ->
            ?f("{\n"
               "    ErlNifBinary b;\n"
               "    if (enif_inspect_binary(env, ~s, &b)) {\n"
               "        ~s\n"
               "    } else if (enif_is_list(env, ~s)) {\n"
               "        if (enif_inspect_iolist_as_binary(env, ~s, &b)) {\n"
               "            ~s\n"
               "        } else {\n"
               "            return 0;\n"
               "        }\n"
               "    } else {\n"
               "        return 0;\n"
               "    }\n"
               "}\n",
               [SrcVar, SetFn(["reinterpret_cast<char *>(b.data)", "b.size"]),
                SrcVar, SrcVar, SetFn(["reinterpret_cast<char *>(b.data)", "b.size"])]);
        {msg, Msg2Name} ->
            #cc_msg{type=CMsg2Type} = dict:fetch(Msg2Name, CCMapping),
            PackFnName = mk_c_fn(p_msg_, Msg2Name),
            NewMsg2 = case Setter of
                          set -> ?f("~s->mutable_~s()", [MsgVar, CxxFName]);
                          add -> ?f("~s->add_~s()", [MsgVar, CxxFName]);
                          {set_var, V} ->
                              ?f("~s = new ~s()", [V, CMsg2Type])
                      end,
            ?f("{\n"
               "    ~s *m2 = ~s;\n"
               "    if (!~s(env, ~s, m2))\n"
               "        return 0;\n"
               "}\n",
               [CMsg2Type, NewMsg2, PackFnName, SrcVar]);
        {map, KeyType, ValueType} ->
            CMapType = mk_cctype_name(FType, CCMapping),
            {KeyVar, ValueVar} = SrcVar,
            PtrDeref = case ValueType of
                           {msg,_} -> "*";
                           _       -> ""
                       end,
            KeyDecl = ?f("~s m2k;", [mk_cctype_name(KeyType, CCMapping)]),
            ValueDecl = ?f("~s ~sm2v;", [mk_cctype_name(ValueType, CCMapping),
                                         PtrDeref]),
            SetKey = format_nif_cc_field_packer_single(
                       KeyVar, MsgVar, Field#?gpb_field{type=KeyType},
                       Defs, CCMapping, Opts,
                       {set_var, "m2k"}),
            SetValue = format_nif_cc_field_packer_single(
                         ValueVar, MsgVar, Field#?gpb_field{type=ValueType},
                         Defs, CCMapping, Opts,
                         {set_var, "m2v"}),
            ["{\n",
             ?f("    ~s *map = ~s->mutable_~s();\n"
                "    ~s\n"  % decl of m2k
                "    ~s\n"  % decl of m2v
                "\n",
                [CMapType, MsgVar, CxxFName,
                 KeyDecl, ValueDecl]),
             %% Set values for m2k and m2v
             SetKey,
             SetValue,
             ?f("    (*map)[m2k] = ~sm2v;\n", [PtrDeref]),
             "}\n"]
    end.

format_nif_cc_field_packer_repeated(SrcVar, MsgVar, Field,
                                    Defs, CCMapping, Opts) ->
    [?f("{\n"
        "    ERL_NIF_TERM l = ~s;\n"
        "\n"
        "    while (!enif_is_empty_list(env, l))\n"
        "    {\n"
        "        ERL_NIF_TERM head, tail;\n"
        "\n"
        "        if (!enif_get_list_cell(env, l, &head, &tail))\n"
        "            return 0;\n",
        [SrcVar]),
     "\n",
     gpb_lib:split_indent_iolist(
       4, format_nif_cc_field_packer_single(
            "head", MsgVar, Field, Defs, CCMapping, Opts, add)),
     ?f("        l = tail;\n"
        "    }\n"
        "}\n",
        [])].

format_nif_cc_field_packer_maptype(SrcVar, MsgVar, Field,
                                   Defs, CCMapping, Opts) ->
    case gpb_lib:get_2tuples_or_maps_for_maptype_fields_by_opts(Opts) of
        '2tuples' ->
            format_nif_cc_field_packer_maptype_r(SrcVar, MsgVar, Field,
                                                 Defs, CCMapping, Opts);
        maps ->
            format_nif_cc_field_packer_maptype_m(SrcVar, MsgVar, Field,
                                                 Defs, CCMapping, Opts)
    end.

format_nif_cc_field_packer_maptype_r(SrcVar, MsgVar, Field,
                                     Defs, CCMapping, Opts) ->
    ?f("{\n"
       "    ERL_NIF_TERM l = ~s;\n\n"
       ""
       "    while (!enif_is_empty_list(env, l))\n"
       "    {\n"
       "        ERL_NIF_TERM head, tail;\n\n"
       ""
       "        if (!enif_get_list_cell(env, l, &head, &tail))\n"
       "            return 0;\n\n"
       ""
       "        int arity;\n"
       "        const ERL_NIF_TERM *tuple;\n"
       "        if (!enif_get_tuple(env, head, &arity, &tuple))\n"
       "            return 0;\n"
       "        if (arity != 2)\n"
       "            return 0;\n"
       "        ~s\n\n"
       ""
       "        l = tail;\n"
       "    }\n"
       "}\n",
       [SrcVar,
        gpb_lib:split_indent_butfirst_iolist(
          8, format_nif_cc_field_packer_single(
               {"tuple[0]", "tuple[1]"}, MsgVar, Field,
               Defs, CCMapping, Opts, add))]).

format_nif_cc_field_packer_maptype_m(SrcVar, MsgVar, Field,
                                     Defs, CCMapping, Opts) ->
    ?f("{\n"
       "    ERL_NIF_TERM ik, iv;\n"
       "    ErlNifMapIterator iter;\n"
       "    ErlNifMapIteratorEntry first;\n\n"
       ""
       "~s\n\n" %% init of iterator `first'
       ""
       "    if (!enif_map_iterator_create(env, ~s, &iter, first))\n"
       "        return 0;\n\n"
       ""
       "    while (enif_map_iterator_get_pair(env, &iter, &ik, &iv))\n"
       "    {\n"
       "        ~s\n"
       "        enif_map_iterator_next(env, &iter);\n"
       "    }\n"
       "    enif_map_iterator_destroy(env, &iter);\n"
       "}\n",
       [initialize_map_iterator(4, "first"),
        SrcVar,
        gpb_lib:split_indent_butfirst_iolist(
          8, format_nif_cc_field_packer_single(
               {"ik", "iv"}, MsgVar, Field, Defs, CCMapping, Opts, add))]).

format_nif_cc_field_packer_unknowns(SrcVar, MsgVar) ->
    ?f("{\n"
       "    if (!pack_unknown_msg_fields(env, ~s, ~s))\n"
       "        return 0;\n"
       "}\n",
       [MsgVar, SrcVar]).

format_nif_cc_decoders(Mod, Defs, CCMapping, Opts) ->
    [format_nif_cc_decoder(Mod, MsgName, Fields, CCMapping, Opts)
     || {{msg, MsgName}, Fields} <- Defs].

format_nif_cc_decoder(_Mod, MsgName, _Fields, CCMapping, _Opts) ->
    FnName = mk_c_fn(decode_msg_, MsgName),
    UnpackFnName = mk_c_fn(u_msg_, MsgName),
    #cc_msg{type=CMsgType} = dict:fetch(MsgName, CCMapping),
    ["static ERL_NIF_TERM\n",
     FnName,"(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])\n",
     "{\n",
     "    ErlNifBinary data;\n",
     "    ERL_NIF_TERM res;\n",
     "    ",CMsgType," *m = new ",CMsgType,"();\n",
     "\n"
     "    if (argc != 1)\n",
     "    {\n",
     "        delete m;\n",
     "        return enif_make_badarg(env);\n",
     "    }\n",
     "\n",
     "    if (m == NULL)\n",
     "    {\n",
     "        delete m;\n",
     "        return enif_make_badarg(env);\n", %% FIXME: enomem?
     "    }\n",
     "\n",
     "    if (!enif_inspect_binary(env, argv[0], &data))\n"
     "    {\n",
     "        delete m;\n",
     "        return enif_make_badarg(env);\n",
     "    }\n",
     "\n",
     "    if (!m->ParseFromArray(data.data, data.size))\n",
     "    {\n",
     "        delete m;\n",
     "        return enif_make_badarg(env);\n",
     "    }\n",
     "\n",
     "    res = ",UnpackFnName,"(env, m);\n",
     "    delete m;\n",
     "    return res;\n",
     "}\n"
     "\n"].

format_nif_cc_unpackers(_Mod, Defs, CCMapping, Opts) ->
    [format_nif_cc_unpacker(MsgName, Fields, Defs, CCMapping, Opts)
     || {_msg_or_group, MsgName, Fields} <- gpb_lib:msgs_or_groups(Defs)].

format_nif_cc_unpacker(MsgName, Fields, Defs, CCMapping, Opts) ->
    Maps = gpb_lib:get_records_or_maps_by_opts(Opts) == maps,
    UnpackFnName = mk_c_fn(u_msg_, MsgName),
    #cc_msg{type=CMsgType} = dict:fetch(MsgName, CCMapping),
    IFields = gpb_lib:index_seq(Fields),
    Is = [I || {I,_} <- IFields],
    %% Initialize the keys to silence "may be used uninitialized"
    %% warnings for oneof fields for maps (not set in the default,
    %% but checked for instead by checking for no_value)
    {KeyVarType, KeyInitExpr} =
        case gpb_lib:get_maps_key_type_by_opts(Opts) of
            binary -> {"const char  *", "NULL"};
            atom   -> {"ERL_NIF_TERM ", "gpb_x_no_value"}
        end,
    ["static ERL_NIF_TERM\n",
     UnpackFnName,"(ErlNifEnv *env, const ",CMsgType," *m)\n",
     "{\n",
     "    ERL_NIF_TERM res;\n",
     [?f("    ERL_NIF_TERM rname = ~s;\n", [mk_c_var(gpb_aa_, MsgName)])
      || not Maps],
     [ ?f("    ERL_NIF_TERM elem~w;\n", [I]) || I <- Is],
     [?f("    ~s"         "key~w = ~s;\n", [KeyVarType, I, KeyInitExpr])
      || I <- Is, Maps],
     "\n",
     %% Unpack each field
     [case Field of
          #?gpb_field{name=FName} ->
              DestVar = ?f("elem~w",[I]),
              gpb_lib:split_indent_iolist(
                4,
                [[?f("key~w = ~s;\n", [I, mk_c_field_var(FName)]) || Maps],
                 format_nif_cc_field_unpacker(DestVar, "m", MsgName, Field,
                                              Defs, CCMapping, Opts)]);
          #gpb_oneof{} ->
              {KSetter, VSetter, UndefSetter} =
                  calc_oneof_key_value_updaters(I, Field, Opts),
              gpb_lib:split_indent_iolist(
                4,
                format_nif_cc_field_oneof_unpacker(
                  "m", MsgName, Field, KSetter, VSetter, UndefSetter,
                  Defs, CCMapping, Opts))
      end
      || {I, Field} <- IFields],
     "\n",
     %% Wrap the result into a record or map
     case gpb_lib:get_mapping_and_unset_by_opts(Opts) of
         records ->
             ?f("    res = enif_make_tuple(env, ~w, rname~s);\n",
                [length(Fields) + 1, [?f(", elem~w",[I]) || I <- Is]]);
         #maps{unset_optional=present_undefined} ->
             [?f("    res = enif_make_new_map(env);\n"),
              [?f("    enif_make_map_put(env, res,\n"
                  "                      mk_key(env, ~s), elem~w,\n"
                  "                      &res);\n",
                  [mk_c_field_var(gpb_lib:get_field_name(Field)), I])
               || {I, Field} <- IFields]];
         #maps{unset_optional=omitted} ->
             [?f("    res = enif_make_new_map(env);\n"),
              [begin
                   Put = ?f("enif_make_map_put(env, res,\n"
                            "                  mk_key(env, key~w),\n"
                            "                  elem~w,\n"
                            "                  &res);",
                            [I, I]),
                   Test = ?f("if (!enif_is_identical(elem~w, gpb_x_no_value))",
                             [I]),
                   PutLine = Put ++ "\n",
                   TestLine = Test ++ "\n",
                   case gpb_lib:get_field_occurrence(Field) of
                       optional ->
                           gpb_lib:indent_lines(
                             4, [TestLine, gpb_lib:indent(4, PutLine)]);
                       defaulty ->
                           gpb_lib:indent_lines(
                             4, [TestLine, gpb_lib:indent(4, PutLine)]);
                       _ ->
                           gpb_lib:indent(4, PutLine)
                   end
               end
               || {I, Field} <- IFields]]
     end,
     "    return res;\n"
     "}\n",
     "\n"].

calc_oneof_key_value_updaters(I, #gpb_oneof{name=FName}, Opts) ->
    case gpb_lib:get_mapping_and_unset_by_opts(Opts) of
        records ->
            {fun(_Tag) -> nop end,
             fun(Tag,V) ->
                     ?f("elem~w = enif_make_tuple2(env, gpb_aa_~s, ~s)",
                        [I, Tag, V])
             end,
             fun() -> ?f("elem~w = gpb_x_no_value", [I]) end};
        #maps{oneof=tuples}  ->
            {fun(_Tag) -> ?f("key~w = ~s", [I, mk_c_field_var(FName)]) end,
             fun(Tag,V) ->
                     ?f("elem~w = enif_make_tuple2(env, gpb_aa_~s, ~s)",
                        [I, Tag, V])
             end,
             fun() -> ?f("elem~w = gpb_x_no_value", [I]) end};
        #maps{oneof=flat} ->
            {fun(Tag) -> ?f("key~w = ~s", [I, mk_c_field_var(Tag)]) end,
             fun(_Tag,V) -> ?f("elem~w = ~s", [I, V]) end,
             fun() -> ?f("elem~w = gpb_x_no_value", [I]) end}
    end.

format_nif_cc_field_unpacker(DestVar, MsgVar, _MsgName, #?gpb_field{}=Field,
                             Defs, CCMapping, Opts) ->
    case categorize_field_kind(Field) of
        required ->
            format_nif_cc_present_field_unpacker_single(DestVar, MsgVar, Field,
                                                        Defs, CCMapping);
        optional ->
            format_nif_cc_optional_field_unpacker_single(DestVar, MsgVar, Field,
                                                         Defs, CCMapping);
        defaulty ->
            format_nif_cc_present_field_unpacker_single(DestVar, MsgVar, Field,
                                                        Defs, CCMapping);
        repeated ->
            format_nif_cc_field_unpacker_repeated(DestVar, MsgVar, Field,
                                                  Defs, CCMapping);
        map ->
            format_nif_cc_field_unpacker_maptype(DestVar, MsgVar, Field,
                                                 Defs, CCMapping, Opts);
        unknowns ->
            format_nif_cc_present_field_unpacker_unknowns(DestVar, MsgVar)
    end.

format_nif_cc_field_oneof_unpacker(MsgVar, MsgName,
                                   #gpb_oneof{name=OFName, fields=OFields},
                                   KSetter, VSetter, UndefSetter,
                                   Defs, CCMapping, _Opts) ->
    #cc_msg{type=CMsgType} = dict:fetch(MsgName, CCMapping),
    UCOFName = to_upper(OFName),
    [?f("switch (~s->~s_case())\n", [MsgVar, OFName]),
     ?f("{\n"),
     [begin
          CamelCaseFOFName = camel_case(FOFName),
          KSetExpr = KSetter(FOFName),
          VSetExpr = VSetter(FOFName, "ores"),
          gpb_lib:split_indent_iolist(
            4,
            [?f("case ~s::k~s:\n", [CMsgType, CamelCaseFOFName]),
             ?f("    {\n"),
             ?f("        ERL_NIF_TERM ores;\n"),
             gpb_lib:split_indent_iolist(
               8,
               [format_nif_cc_field_unpacker_by_field("ores", MsgVar,
                                                      OField, Defs, CCMapping),
                [[KSetExpr,";\n"] || KSetExpr /= nop],
                [VSetExpr,";\n"]]),
             ?f("    }\n"),
             ?f("    break;\n\n")])
      end
      || #?gpb_field{name=FOFName}=OField <- OFields],
     gpb_lib:split_indent_iolist(
       4,
       [?f("case ~s::~s_NOT_SET: /* FALL THROUGH */~n", [CMsgType, UCOFName]),
        ?f("default:~n"),
        gpb_lib:split_indent_iolist(4, [UndefSetter(),";\n"])]),
     ?f("}\n"),
     "\n"].

format_nif_cc_present_field_unpacker_single(DestVar, MsgVar, Field,
                                            Defs, CCMapping) ->
    [format_nif_cc_field_unpacker_by_field(DestVar, MsgVar, Field,
                                           Defs, CCMapping),
     "\n"].

format_nif_cc_optional_field_unpacker_single(DestVar, MsgVar, Field,
                                             Defs, CCMapping) ->
    #?gpb_field{name=FName} = Field,
    CxxFName = 'field_name_to_c++'(FName),
    ?f("if (!~s->has_~s())\n"
       "    ~s = gpb_x_no_value;\n"
       "else\n"
       "~s\n"
       "\n", [MsgVar, CxxFName, DestVar,
              split_indent_iolist_unless_curly_block(
                4, format_nif_cc_field_unpacker_by_field(
                     DestVar, MsgVar, Field, Defs, CCMapping))]).

format_nif_cc_field_unpacker_by_field(DestVar, MsgVar, Field,
                                      Defs, CCMapping) ->
    #?gpb_field{name=FName, type=FType} = Field,
    CxxFName = 'field_name_to_c++'(FName),
    SrcExpr = ?f("~s->~s()", [MsgVar, CxxFName]),
    format_nif_cc_field_unpacker_by_type(DestVar, SrcExpr, FType,
                                         Defs, CCMapping).

format_nif_cc_field_unpacker_by_type(DestVar, SrcExpr, FType,
                                     Defs, CCMapping) ->
    case FType of
        float ->
            [?f("{\n"),
             ?f("    float v = ~s;\n", [SrcExpr]),
             ?f("    if (isnan(v))\n"),
             ?f("        ~s = gpb_aa_nan;\n", [DestVar]),
             ?f("    else if (isinf(v) && v < 0)\n", []),
             ?f("        ~s = gpb_aa_minfinity;\n", [DestVar]),
             ?f("    else if (isinf(v))\n"),
             ?f("        ~s = gpb_aa_infinity;\n", [DestVar]),
             ?f("    else\n", []),
             ?f("        ~s = enif_make_double(env, (double)v);\n", [DestVar]),
             ?f("}\n")];
        double ->
            [?f("{\n"),
             ?f("    double v = ~s;\n", [SrcExpr]),
             ?f("    if (isnan(v))\n"),
             ?f("        ~s = gpb_aa_nan;\n", [DestVar]),
             ?f("    else if (isinf(v) && v < 0)\n", []),
             ?f("        ~s = gpb_aa_minfinity;\n", [DestVar]),
             ?f("    else if (isinf(v))\n"),
             ?f("        ~s = gpb_aa_infinity;\n", [DestVar]),
             ?f("    else\n", []),
             ?f("        ~s = enif_make_double(env, v);\n", [DestVar]),
             ?f("}\n")];
        _S32 when FType == sint32;
                  FType == int32;
                  FType == sfixed32 ->
            [?f("~s = enif_make_int(env, ~s);\n",
                [DestVar, SrcExpr])];
        _S64 when FType == sint64;
                  FType == int64;
                  FType == sfixed64 ->
            [?f("~s = enif_make_int64(env, (ErlNifSInt64)~s);\n",
                [DestVar, SrcExpr])];
        _U32 when FType == uint32;
                  FType == fixed32 ->
            [?f("~s = enif_make_uint(env, ~s);\n",
                [DestVar, SrcExpr])];
        _U64 when FType == uint64;
                  FType == fixed64 ->
            [?f("~s = enif_make_uint64(env, (ErlNifUInt64)~s);\n",
                [DestVar, SrcExpr])];
        bool ->
            [?f("if (~s)\n", [SrcExpr]),
             ?f("    ~s = gpb_aa_true;\n", [DestVar]),
             ?f("else\n"),
             ?f("    ~s = gpb_aa_false;\n", [DestVar])];
        {enum, EnumName} ->
            #cc_enum{unaliased_enums=CCEnums} =
                dict:fetch(EnumName, CCMapping),
            [] ++
                [?f("switch (~s) {\n", [SrcExpr])] ++
                [?f("    case ~s: ~s = ~s; break;\n",
                    [CCSym, DestVar, mk_c_var(gpb_aa_, Sym)])
                 || {Sym, CCSym} <- CCEnums] ++
                [?f("    default: ~s = gpb_aa_undefined;\n", [DestVar])] ++
                [?f("}\n")];
        string ->
            [?f("{\n"),
             ?f("    const char    *sData = ~s.data();\n", [SrcExpr]),
             ?f("    unsigned int   sSize = ~s.size();\n", [SrcExpr]),
             ?f("    ~s = utf8_to_erl_string(env, sData, sSize);\n", [DestVar]),
             ?f("}\n")];
        bytes ->
            [?f("{\n"),
             ?f("    unsigned char *data;\n"),
             ?f("    unsigned int   bSize = ~s.size();\n", [SrcExpr]),
             ?f("    const char    *bData = ~s.data();\n", [SrcExpr]),
             ?f("    data = enif_make_new_binary(\n"), %% can data be NULL??
             ?f("               env,\n"),
             ?f("               bSize,\n"),
             ?f("               &~s);\n", [DestVar]),
             ?f("    memmove(data, bData, bSize);\n"),
             ?f("}\n")];
        {msg, Msg2Name} ->
            UnpackFnName = mk_c_fn(u_msg_, Msg2Name),
            [?f("~s = ~s(env, &~s);\n",
                [DestVar, UnpackFnName, SrcExpr])];
        {group, Name} ->
            FType1 = {msg,Name},
            format_nif_cc_field_unpacker_by_type(DestVar, SrcExpr, FType1,
                                                 Defs, CCMapping)
    end.

format_nif_cc_field_unpacker_repeated(DestVar, MsgVar, Field,
                                      Defs, CCMapping) ->
    #?gpb_field{name=FName, type=FType} = Field,
    CxxFName = 'field_name_to_c++'(FName),
    [?f("{\n"),
     ?f("    unsigned int numElems = ~s->~s_size();\n", [MsgVar, CxxFName]),
     ?f("    ERL_NIF_TERM relem[numElems];\n"),
     ?f("    unsigned int i;\n"),
     "\n",
     ?f("    for (i = 0; i < numElems; i++)\n"),
     gpb_lib:split_indent_iolist(
       4, split_indent_iolist_unless_curly_block(
            4, format_nif_cc_field_unpacker_by_type(
                 "relem[i]", ?f("~s->~s(i)", [MsgVar, CxxFName]),
                 FType, Defs, CCMapping))),
     ?f("    ~s = enif_make_list_from_array(env, relem, numElems);\n",
        [DestVar]),
     "}\n",
     "\n"].

format_nif_cc_field_unpacker_maptype(DestVar, MsgVar, Field,
                                     Defs, CCMapping, Opts) ->
    #?gpb_field{name=FName, type={map, KeyType, ValueType}=Type} = Field,
    CxxFName = 'field_name_to_c++'(FName),
    ItType = mk_cctype_name(Type, CCMapping) ++ "::const_iterator",
    MapsOrTuples = gpb_lib:get_2tuples_or_maps_for_maptype_fields_by_opts(Opts),
    ["{\n",
     gpb_lib:split_indent_iolist(
       4,
       case MapsOrTuples of
           '2tuples' ->
               [?f("~s = enif_make_list(env, 0);\n", [DestVar]),
                ?f("int i = 0;\n", [])];
           maps ->
               ?f("~s = enif_make_new_map(env);\n", [DestVar])
       end),
     %% Iterate
     ?f("    for (~s it = ~s->~s().begin();\n"
        "         it != ~s->~s().end();\n"
        "         ++it)\n",
        [ItType, MsgVar, CxxFName, MsgVar, CxxFName]),
     "    {\n",
     "        ERL_NIF_TERM ek, ev;\n",
     %% FIXME
     gpb_lib:split_indent_iolist(
       8,
       [format_nif_cc_field_unpacker_by_type("ek", "it->first", KeyType,
                                             Defs, CCMapping),
        format_nif_cc_field_unpacker_by_type("ev", "it->second", ValueType,
                                             Defs, CCMapping),
        case MapsOrTuples of
            '2tuples' ->
                ["ERL_NIF_TERM eitem = enif_make_tuple2(env, ek, ev);\n",
                 ?f("~s = enif_make_list_cell(env, eitem, ~s);\n",
                    [DestVar, DestVar]),
                 "++i;\n"];
            maps ->
                [?f("enif_make_map_put(env, ~s, ek, ev, &~s);\n",
                    [DestVar, DestVar])]
        end]),
     "    }\n",
     "}\n"].

format_nif_cc_present_field_unpacker_unknowns(DestVar, MsgVar) ->
    ?f("~s = unpack_unknown_msg_fields(env, ~s);\n",
       [DestVar, MsgVar]).

format_nif_cc_to_jsoners(Mod, Defs, CCMapping, Opts) ->
    [format_nif_cc_to_jsoner(Mod, MsgName, Fields, CCMapping, Opts)
     || {{msg, MsgName}, Fields} <- Defs].

format_nif_cc_to_jsoner(_Mod, MsgName, _Fields, CCMapping, Opts) ->
    FnName = mk_c_fn(to_json_msg_, MsgName),
    PackFnName = mk_c_fn(p_msg_, MsgName),
    #cc_msg{type=CMsgType} = dict:fetch(MsgName, CCMapping),
    AlwaysPrintPrimitives =
        atom_to_list(
          proplists:get_bool(json_always_print_primitive_fields, Opts)),
    PreserveFNames =
        atom_to_list(proplists:get_bool(json_preserve_proto_field_names, Opts)),
    %% The preserve_proto_field_names and always_print_enums_as_ints
    %% JsonPrintOptions fields were added in in 3.3.0, so check for that.
    %% There is an error check earlier for when it needs to be set to true.
    %% Here, just don't fail if in the interval 3.0.0 .. 3.2.x.
    ["static ERL_NIF_TERM\n",
     FnName,"(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])\n",
     "{\n",
     "    ERL_NIF_TERM result;\n",
     "    ",CMsgType," *m = new ",CMsgType,"();\n",
     "    ::std::string j;\n"
     "    ::google::protobuf::util::JsonPrintOptions jopts;\n\n"
     ""
     "    jopts.add_whitespace = false;\n"
     "    jopts.always_print_primitive_fields = ",AlwaysPrintPrimitives,";\n"
     "#if GOOGLE_PROTOBUF_VERSION >= 3003000\n"
     "    jopts.preserve_proto_field_names = ",PreserveFNames,";\n"
     "    jopts.always_print_enums_as_ints = false;\n\n"
     "#endif\n"
     ""
     "    if (argc != 1)\n"
     "    {\n"
     "        delete m;\n"
     "        return enif_make_badarg(env);\n"
     "    }\n\n"
     ""
     "    if (m == NULL)\n"
     "    {\n"
     "        delete m;\n"
     "        return enif_make_badarg(env);\n"
     "    }\n\n"
     ""
     "    if (!",PackFnName,"(env, argv[0], m))\n"
     "    {\n"
     "        delete m;\n"
     "        return enif_make_badarg(env);\n"
     "    }\n\n"
     ""
     "    if (::google::protobuf::util::MessageToJsonString(*m, &j, jopts)\n"
     "        == ::google::protobuf::util::Status::OK)\n"
     "    {\n"
     "        unsigned char *data;\n"
     "        const char    *jData = j.data();\n"
     "        unsigned int   jSize = j.size();\n"
     "        data = enif_make_new_binary(env, jSize, &result);\n"
     "        memmove(data, jData, jSize);\n"
     "    }\n"
     "    else\n"
     "    {\n"
     "        delete m;\n"
     "        return enif_make_badarg(env);\n"
     "    }\n\n"
     ""
     "    delete m;\n"
     "    return result;\n"
     "}\n"
     "\n"].

format_nif_cc_from_jsoners(Mod, Defs, CCMapping, Opts) ->
    [format_nif_cc_from_jsoner(Mod, MsgName, Fields, CCMapping, Opts)
     || {{msg, MsgName}, Fields} <- Defs].

format_nif_cc_from_jsoner(_Mod, MsgName, _Fields, CCMapping, Opts) ->
    FnName = mk_c_fn(from_json_msg_, MsgName),
    UnpackFnName = mk_c_fn(u_msg_, MsgName),
    #cc_msg{type=CMsgType} = dict:fetch(MsgName, CCMapping),
    CaseInsensitiveEnums =
        atom_to_list(
          proplists:get_bool(json_case_insensitive_enum_parsing, Opts)),
    ["static ERL_NIF_TERM\n",
     FnName,"(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])\n",
     "{\n",
     "    ErlNifBinary data;\n",
     "    ERL_NIF_TERM res;\n",
     "    ",CMsgType," *m = new ",CMsgType,"();\n",
     "    ::std::string j;\n"
     "    ::google::protobuf::util::JsonParseOptions jopts;\n"
     "\n"
     %% The JsonParseOptions contained ignore_unknown_fields already in 3.0.0
     "    jopts.ignore_unknown_fields = true;\n"
     %% Case insensitive enum parsing is default in 3.8.0,
     %% and the option appeared in 3.7.0
     "#if GOOGLE_PROTOBUF_VERSION >= 3007000\n"
     "    jopts.case_insensitive_enum_parsing = ",CaseInsensitiveEnums,";\n\n"
     "#endif\n"
     "\n"
     "    if (argc != 1)\n",
     "    {\n",
     "        delete m;\n",
     "        return enif_make_badarg(env);\n",
     "    }\n",
     "\n",
     "    if (m == NULL)\n",
     "    {\n",
     "        delete m;\n",
     "        return enif_make_badarg(env);\n", %% FIXME: enomem?
     "    }\n",
     "\n",
     "    if (!enif_inspect_binary(env, argv[0], &data))\n"
     "    {\n",
     "        delete m;\n",
     "        return enif_make_badarg(env);\n",
     "    }\n",
     "\n",
     %% try/catch bad_alloc and return enomem?
     "    j.assign(reinterpret_cast<char *>(data.data), data.size);\n"
     "    if (::google::protobuf::util::JsonStringToMessage(j, m, jopts)\n"
     "        != ::google::protobuf::util::Status::OK)"
     "    {\n"
     "        delete m;\n"
     "        return enif_make_badarg(env);\n"
     "    }\n"
     "\n"
     "    res = ",UnpackFnName,"(env, m);\n",
     "    delete m;\n",
     "    return res;\n",
     "}\n"
     "\n"].

mk_cctype_name({enum,EnumName}, CCMapping) ->
    #cc_enum{type = CCType} = dict:fetch(EnumName, CCMapping),
    CCType;
mk_cctype_name({msg,MsgName}, CCMapping) ->
    #cc_msg{type = CCType} = dict:fetch(MsgName, CCMapping),
    CCType;
mk_cctype_name({group,GName}, CCMapping) ->
    #cc_msg{type = CCType} = dict:fetch(GName, CCMapping),
    CCType;
mk_cctype_name({map,KeyType,ValueType}, CCMapping) ->
    CKeyType = mk_cctype_name(KeyType, CCMapping),
    CValueType = mk_cctype_name(ValueType, CCMapping),
    "::google::protobuf::Map< " ++ CKeyType ++ ", " ++ CValueType ++ " >";
mk_cctype_name(Type, _CCMapping) ->
    case Type of
        sint32   -> "::google::protobuf::int32";
        sint64   -> "::google::protobuf::int64";
        int32    -> "::google::protobuf::int32";
        int64    -> "::google::protobuf::int64";
        uint32   -> "::google::protobuf::uint32";
        uint64   -> "::google::protobuf::uint64";
        bool     -> "bool";
        fixed64  -> "::google::protobuf::uint64";
        sfixed64 -> "::google::protobuf::int64";
        double   -> "double";
        string   -> "::std::string";
        bytes    -> "::std::string";
        fixed32  -> "::google::protobuf::uint32";
        sfixed32 -> "::google::protobuf::int32";
        float    -> "float"
    end.

categorize_field_kind(#?gpb_field{occurrence=Occurrence, type=Type}=Field) ->
    case Occurrence of
        required ->
            required;
        optional ->
            optional;
        defaulty ->
            defaulty;
        repeated ->
            case Type of
                {map, _, _} ->
                    map;
                _ ->
                    case gpb_lib:is_field_for_unknowns(Field) of
                        true ->
                            unknowns;
                        false ->
                            repeated
                    end
            end
    end.

initialize_map_iterator(Indent, IteratorVarName) ->
    ?f("#if ~s\n"
       "~s = ERL_NIF_MAP_ITERATOR_FIRST;\n"
       "#else /* before 2.8 which appeared in 18.0 */\n"
       "~s = ERL_NIF_MAP_ITERATOR_HEAD;\n"
       "#endif\n",
       [format_nif_check_version_or_later(2, 8),
        gpb_lib:indent(Indent, IteratorVarName),
        gpb_lib:indent(Indent, IteratorVarName)]).

split_indent_iolist_unless_curly_block(Indent, IoList) ->
    gpb_lib:cond_split_indent_iolist(
      fun is_not_curly_block/1, Indent, IoList).

is_not_curly_block(<<"{", _/binary>>) -> false;
is_not_curly_block(_) -> true.

to_lower(A) when is_atom(A) ->
    list_to_atom(gpb_lib:lowercase(atom_to_list(A))).

to_upper(A) when is_atom(A) ->
    list_to_atom(gpb_lib:uppercase(atom_to_list(A))).

camel_case(A) when is_atom(A) ->
    list_to_atom(gpb_lib:camel_case(atom_to_list(A))).

mk_c_fn(Prefix, Suffix) ->
    dot_to_underscore(lists:concat([Prefix, Suffix])).

mk_c_var(Prefix, Suffix) ->
    dot_to_underscore(lists:concat([Prefix, Suffix])).

dot_to_underscore(X) when is_list(X) -> dot_replace_s(X, "_").

dot_replace_s(S, New) when is_list(S) -> d_r(S, New);
dot_replace_s(S, New) when is_atom(S) -> d_r(atom_to_list(S), New).

d_r("."++Rest, New) -> New ++ d_r(Rest, New);
d_r([C|Rest], New)  -> [C | d_r(Rest, New)];
d_r("", _New)       -> "".

is_dotted(S) when is_list(S) -> gpb_lib:is_substr(".", S).

'field_name_to_c++'(FName) ->
    'sym_to_c++'(to_lower(FName)).

'sym_to_c++'(Sym) ->
    case 'is_c++_keyword'(Sym) of
        false ->
            Sym;
        true ->
            underscore_suffix(Sym)
    end.

mk_c_field_var(A) ->
    S = atom_to_list(A),
    HasOnlyVarChars = lists:all(fun is_c_var_char/1, S),
    if HasOnlyVarChars ->
            mk_c_var(gpb_xa_, A);
       true ->
            mk_c_var(gpb_xa_, transform_non_c_chars(S))
    end.

is_c_var_char(UC) when $A =< UC, UC =< $Z -> true;
is_c_var_char(LC) when $a =< LC, LC =< $z -> true;
is_c_var_char(D)  when $0 =< D, D =< $9 -> true;
is_c_var_char($_) -> true;
is_c_var_char(_) -> false.

transform_non_c_chars(S) ->
    lists:flatten([transform_c_var_char(C) || C <- S]).

transform_c_var_char(C) ->
    case is_c_var_char(C) of
        true  -> C;
        false -> ?f("x~2.16.0b", [C])
    end.

underscore_suffix(A) when is_atom(A) ->
    list_to_atom(atom_to_list(A) ++ "_").

'is_c++_keyword'(alignas) -> true;
'is_c++_keyword'(alignof) -> true;
'is_c++_keyword'('and') -> true;
'is_c++_keyword'(and_eq) -> true;
'is_c++_keyword'(asm) -> true;
'is_c++_keyword'(auto) -> true;
'is_c++_keyword'(bitand) -> true;
'is_c++_keyword'(bitor) -> true;
'is_c++_keyword'(bool) -> true;
'is_c++_keyword'(break) -> true;
'is_c++_keyword'('case') -> true;
'is_c++_keyword'('catch') -> true;
'is_c++_keyword'(char) -> true;
'is_c++_keyword'(class) -> true;
'is_c++_keyword'(compl) -> true;
'is_c++_keyword'(const) -> true;
'is_c++_keyword'(constexpr) -> true;
'is_c++_keyword'(const_cast) -> true;
'is_c++_keyword'(continue) -> true;
'is_c++_keyword'(decltype) -> true;
'is_c++_keyword'(default) -> true;
'is_c++_keyword'(delete) -> true;
'is_c++_keyword'(do) -> true;
'is_c++_keyword'(double) -> true;
'is_c++_keyword'(dynamic_cast) -> true;
'is_c++_keyword'(else) -> true;
'is_c++_keyword'(enum) -> true;
'is_c++_keyword'(explicit) -> true;
'is_c++_keyword'(export) -> true;
'is_c++_keyword'(extern) -> true;
'is_c++_keyword'(false) -> true;
'is_c++_keyword'(float) -> true;
'is_c++_keyword'(for) -> true;
'is_c++_keyword'(friend) -> true;
'is_c++_keyword'(goto) -> true;
'is_c++_keyword'('if') -> true;
'is_c++_keyword'(inline) -> true;
'is_c++_keyword'(int) -> true;
'is_c++_keyword'(long) -> true;
'is_c++_keyword'(mutable) -> true;
'is_c++_keyword'(namespace) -> true;
'is_c++_keyword'(new) -> true;
'is_c++_keyword'(noexcept) -> true;
'is_c++_keyword'('not') -> true;
'is_c++_keyword'(not_eq) -> true;
'is_c++_keyword'(nullptr) -> true;
'is_c++_keyword'(operator) -> true;
'is_c++_keyword'('or') -> true;
'is_c++_keyword'(or_eq) -> true;
'is_c++_keyword'(private) -> true;
'is_c++_keyword'(protected) -> true;
'is_c++_keyword'(public) -> true;
'is_c++_keyword'(register) -> true;
'is_c++_keyword'(reinterpret_cast) -> true;
'is_c++_keyword'(return) -> true;
'is_c++_keyword'(short) -> true;
'is_c++_keyword'(signed) -> true;
'is_c++_keyword'(sizeof) -> true;
'is_c++_keyword'(static) -> true;
'is_c++_keyword'(static_assert) -> true;
'is_c++_keyword'(static_cast) -> true;
'is_c++_keyword'(struct) -> true;
'is_c++_keyword'(switch) -> true;
'is_c++_keyword'(template) -> true;
'is_c++_keyword'(this) -> true;
'is_c++_keyword'(thread_local) -> true;
'is_c++_keyword'(throw) -> true;
'is_c++_keyword'(true) -> true;
'is_c++_keyword'('try') -> true;
'is_c++_keyword'(typedef) -> true;
'is_c++_keyword'(typeid) -> true;
'is_c++_keyword'(typename) -> true;
'is_c++_keyword'(union) -> true;
'is_c++_keyword'(unsigned) -> true;
'is_c++_keyword'(using) -> true;
'is_c++_keyword'(virtual) -> true;
'is_c++_keyword'(void) -> true;
'is_c++_keyword'(volatile) -> true;
'is_c++_keyword'(wchar_t) -> true;
'is_c++_keyword'(while) -> true;
'is_c++_keyword'('xor') -> true;
'is_c++_keyword'(xor_eq) -> true;
'is_c++_keyword'(_) -> false.
